Skip to content

Commit dbe5818

Browse files
trueadmRich-Harris
andauthored
fix: wrap each block expression in derived to encapsulte effects (#14967)
* fix: wrap each block expression in derived to encapsulte effects * add test * Update .changeset/tender-apples-scream.md --------- Co-authored-by: Rich Harris <[email protected]>
1 parent dc8ae82 commit dbe5818

File tree

4 files changed

+78
-8
lines changed

4 files changed

+78
-8
lines changed

.changeset/tender-apples-scream.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: wrap each block expression in derived to encapsulate effects

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ import { source, mutable_source, internal_set } from '../../reactivity/sources.j
3535
import { array_from, is_array } from '../../../shared/utils.js';
3636
import { INERT } from '../../constants.js';
3737
import { queue_micro_task } from '../task.js';
38-
import { active_effect, active_reaction } from '../../runtime.js';
38+
import { active_effect, active_reaction, get } from '../../runtime.js';
3939
import { DEV } from 'esm-env';
40+
import { derived_safe_equal } from '../../reactivity/deriveds.js';
4041

4142
/**
4243
* The row of a keyed each block that is currently updating. We track this
@@ -135,15 +136,17 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
135136

136137
var was_empty = false;
137138

138-
block(() => {
139+
// TODO: ideally we could use derived for runes mode but because of the ability
140+
// to use a store which can be mutated, we can't do that here as mutating a store
141+
// will still result in the collection array being the same from the store
142+
var each_array = derived_safe_equal(() => {
139143
var collection = get_collection();
140144

141-
var array = is_array(collection)
142-
? collection
143-
: collection == null
144-
? []
145-
: array_from(collection);
145+
return is_array(collection) ? collection : collection == null ? [] : array_from(collection);
146+
});
146147

148+
block(() => {
149+
var array = get(each_array);
147150
var length = array.length;
148151

149152
if (was_empty && length === 0) {
@@ -254,7 +257,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
254257
// that a mutation occurred and it's made the collection MAYBE_DIRTY, so reading the
255258
// collection again can provide consistency to the reactive graph again as the deriveds
256259
// will now be `CLEAN`.
257-
get_collection();
260+
get(each_array);
258261
});
259262

260263
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, logs }) {
6+
const [btn1] = target.querySelectorAll('button');
7+
8+
btn1.click();
9+
flushSync();
10+
11+
await Promise.resolve();
12+
await Promise.resolve();
13+
14+
assert.deepEqual(logs, ['cleanup']);
15+
}
16+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script>
2+
import { createSubscriber } from 'svelte/reactivity';
3+
4+
class MyStore {
5+
#subscribe;
6+
#data = $state([
7+
['a', [1, 2]],
8+
['b', [3, 4]]
9+
]);
10+
#id;
11+
12+
constructor(options) {
13+
options?.someBoolean;
14+
this.#id = options?.id;
15+
this.#subscribe = createSubscriber(() => {
16+
debugger
17+
return () => {
18+
console.log('cleanup');
19+
};
20+
});
21+
}
22+
23+
get data() {
24+
this.#subscribe();
25+
return this.#data;
26+
}
27+
set data(v) {
28+
this.#data = v;
29+
}
30+
}
31+
32+
let storeOptions = $state({
33+
someBoolean: false,
34+
id: 0
35+
});
36+
37+
let myStore = $derived(new MyStore(storeOptions));
38+
</script>
39+
40+
<button
41+
onclick={() => {
42+
storeOptions.someBoolean = !storeOptions.someBoolean;
43+
}}>+</button
44+
>
45+
46+
{#each myStore.data as _}{/each}

0 commit comments

Comments
 (0)