Skip to content

Commit 531ff62

Browse files
fix: do no rerun the each block when array change from empty to empty (#13553)
* do no rerun the each block when array change from empty to empty * rename empty yo was_empty * add test * fix nullable array on SSR * format * rewrite * chore: add changeset --------- Co-authored-by: Paolo Ricciuti <[email protected]>
1 parent 6776947 commit 531ff62

File tree

5 files changed

+122
-3
lines changed

5 files changed

+122
-3
lines changed

.changeset/loud-walls-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: do no rerun the each block when array change from empty to empty

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
132132
/** @type {Effect | null} */
133133
var fallback = null;
134134

135+
var was_empty = false;
136+
135137
block(() => {
136138
var collection = get_collection();
137139

@@ -143,6 +145,13 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
143145

144146
var length = array.length;
145147

148+
if (was_empty && length === 0) {
149+
// ignore updates if the array is empty,
150+
// and it already was empty on previous run
151+
return;
152+
}
153+
was_empty = length === 0;
154+
146155
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
147156
let mismatch = false;
148157

packages/svelte/src/internal/server/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,12 @@ export { await_block as await };
507507

508508
/** @param {any} array_like_or_iterator */
509509
export function ensure_array_like(array_like_or_iterator) {
510-
return array_like_or_iterator?.length !== undefined
511-
? array_like_or_iterator
512-
: Array.from(array_like_or_iterator);
510+
if (array_like_or_iterator) {
511+
return array_like_or_iterator.length !== undefined
512+
? array_like_or_iterator
513+
: Array.from(array_like_or_iterator);
514+
}
515+
return [];
513516
}
514517

515518
/**
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
// https://github.com/sveltejs/svelte/issues/13550
5+
// https://github.com/sveltejs/svelte/pull/13553
6+
export default test({
7+
html: `<button>clicks: 0</button><button>undefined</button><button>null</button><button>empty</button><button>[1,2,3]</button><ul><li>count = <span>0</span></li></ul>`,
8+
9+
async test({ assert, target }) {
10+
const [increment, set_undefined, set_null, set_empty, set_list] =
11+
target.querySelectorAll('button');
12+
13+
let [span] = target.querySelectorAll('span');
14+
15+
// initial value
16+
assert.exists(span);
17+
assert.equal(span.innerHTML, '0');
18+
19+
// increment value
20+
flushSync(() => increment.click());
21+
assert.equal(span.innerHTML, '1');
22+
23+
// change collection to undefined
24+
flushSync(() => set_undefined.click());
25+
// increment value
26+
flushSync(() => increment.click());
27+
assert.equal(span.innerHTML, '2');
28+
29+
// change collection to null
30+
flushSync(() => set_null.click());
31+
// increment value
32+
flushSync(() => increment.click());
33+
assert.equal(span.innerHTML, '3');
34+
35+
// change collection to empty
36+
flushSync(() => set_empty.click());
37+
// increment value
38+
flushSync(() => increment.click());
39+
assert.equal(span.innerHTML, '4');
40+
41+
// change collection to undefined
42+
flushSync(() => set_undefined.click());
43+
// increment value
44+
flushSync(() => increment.click());
45+
assert.equal(span.innerHTML, '5');
46+
47+
// change collection to [1,2,3]
48+
flushSync(() => set_list.click());
49+
[span] = target.querySelectorAll('span');
50+
assert.notExists(span);
51+
assert.equal(target.querySelectorAll('li').length, 3);
52+
53+
// change collection to undefined
54+
flushSync(() => set_undefined.click());
55+
[span] = target.querySelectorAll('span');
56+
assert.exists(span);
57+
assert.equal(span.innerHTML, '5');
58+
59+
// increment value
60+
flushSync(() => increment.click());
61+
assert.equal(span.innerHTML, '6');
62+
63+
// change collection to null
64+
flushSync(() => set_null.click());
65+
// increment value
66+
flushSync(() => increment.click());
67+
assert.equal(span.innerHTML, '7');
68+
69+
// change collection to empty
70+
flushSync(() => set_empty.click());
71+
// increment value
72+
flushSync(() => increment.click());
73+
assert.equal(span.innerHTML, '8');
74+
75+
assert.htmlEqual(
76+
target.innerHTML,
77+
`<button>clicks: 8</button><button>undefined</button><button>null</button><button>empty</button><button>[1,2,3]</button><ul><li>count = <span>8</span></li></ul>`
78+
);
79+
}
80+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script>
2+
let list = $state()
3+
let count = $state(0);
4+
5+
function increment() {
6+
count += 1;
7+
}
8+
</script>
9+
10+
<button onclick={increment}>clicks: {count}</button>
11+
<button onclick={()=>list=undefined}>undefined</button>
12+
<button onclick={()=>list=null}>null</button>
13+
<button onclick={()=>list=[]}>empty</button>
14+
<button onclick={()=>list=[1,2,3]}>[1,2,3]</button>
15+
16+
<ul>
17+
{#each list as a}
18+
<li>item : {a}</li>
19+
{:else}
20+
<li>count = <span>{count}</span></li>
21+
{/each}
22+
</ul>

0 commit comments

Comments
 (0)