Skip to content

Commit 75fecf8

Browse files
authored
fix: ensure each block consistency to internal mutations to the collection (#13614)
* fix: ensure bind_checked defers mutation to ensure reactive graph stability * better fix * better fix * better fix * better fix * lint * simplify
1 parent ed790ee commit 75fecf8

File tree

5 files changed

+64
-0
lines changed

5 files changed

+64
-0
lines changed

.changeset/happy-eggs-rest.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: ensure each block consistency to internal mutations to the collection

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,14 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
225225
// continue in hydration mode
226226
set_hydrating(true);
227227
}
228+
229+
// When we mount the each block for the first time, the collection won't be
230+
// connected to this effect as the effect hasn't finished running yet and its deps
231+
// won't be assigned. However, it's possible that when reconciling the each block
232+
// that a mutation occurred and it's made the collection MAYBE_DIRTY, so reading the
233+
// collection again can provide consistency to the reactive graph again as the deriveds
234+
// will now be `CLEAN`.
235+
get_collection();
228236
});
229237

230238
if (hydrating) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
const button = target.querySelector('button');
7+
8+
button?.click();
9+
flushSync();
10+
11+
assert.htmlEqual(
12+
target.innerHTML,
13+
`<button>Add</button><label><input type="checkbox"></label><label><input type="checkbox"></label>`
14+
);
15+
}
16+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
let { value = $bindable() } = $props();
3+
</script>
4+
5+
<label>
6+
<input type="checkbox" bind:checked={value} />
7+
</label>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script>
2+
import Checkbox from './checkbox.svelte';
3+
4+
let foo = $state({})
5+
6+
const schema = $state({
7+
foo: true,
8+
})
9+
10+
function retrieveSchema() {
11+
const cloned = { ...schema }
12+
for (const key of Object.keys(foo)) {
13+
cloned[key] = key
14+
}
15+
return cloned
16+
}
17+
18+
const keys = $derived(Object.keys(retrieveSchema()));
19+
let nextKey = 1;
20+
</script>
21+
22+
<button onclick={() => {
23+
foo[nextKey++] = true
24+
}}>Add</button>
25+
26+
{#each keys as key (key)}
27+
<Checkbox bind:value={foo[key]} />
28+
{/each}

0 commit comments

Comments
 (0)