Skip to content

Commit 5419610

Browse files
authored
fix: ensure resume effects are scheduled in topological order (#15012)
* fix: ensure resume effects are scheduled in topological order * fix: ensure resume effects are scheduled in topological order
1 parent 360ee70 commit 5419610

File tree

4 files changed

+89
-6
lines changed

4 files changed

+89
-6
lines changed

.changeset/lemon-llamas-reflect.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 resume effects are scheduled in topological order

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -602,17 +602,21 @@ export function resume_effect(effect) {
602602
*/
603603
function resume_children(effect, local) {
604604
if ((effect.f & INERT) === 0) return;
605+
effect.f ^= INERT;
606+
607+
// Ensure the effect is marked as clean again so that any dirty child
608+
// effects can schedule themselves for execution
609+
if ((effect.f & CLEAN) === 0) {
610+
effect.f ^= CLEAN;
611+
}
605612

606613
// If a dependency of this effect changed while it was paused,
607-
// apply the change now
614+
// schedule the effect to update
608615
if (check_dirtiness(effect)) {
609-
update_effect(effect);
616+
set_signal_status(effect, DIRTY);
617+
schedule_effect(effect);
610618
}
611619

612-
// Ensure we toggle the flag after possibly updating the effect so that
613-
// each block logic can correctly operate on inert items
614-
effect.f ^= INERT;
615-
616620
var child = effect.first;
617621

618622
while (child !== null) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { flushSync } from '../../../../src/index-client.js';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, raf, target, logs }) {
6+
assert.htmlEqual(
7+
target.innerHTML,
8+
'<button>Toggle</button><div><div>1</div><div>2</div><div>3</div></div>'
9+
);
10+
11+
const btn1 = target.querySelector('button');
12+
btn1?.click();
13+
flushSync();
14+
raf.tick(250);
15+
16+
assert.htmlEqual(
17+
target.innerHTML,
18+
'<button>Toggle</button><div style="opacity: 0.5;"><div>1</div><div>2</div><div>3</div></div>'
19+
);
20+
21+
logs.length = 0;
22+
23+
await Promise.resolve();
24+
25+
flushSync();
26+
raf.tick(500);
27+
28+
assert.htmlEqual(
29+
target.innerHTML,
30+
'<button>Toggle</button><div style=""><div>3</div><div>4</div></div>'
31+
);
32+
33+
assert.deepEqual(logs, ['$effect.pre', '$effect.pre', '$effect', '$effect']);
34+
}
35+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script>
2+
function fade(_) {
3+
return {
4+
duration: 500,
5+
css: t => `opacity: ${t}`,
6+
}
7+
}
8+
9+
let toggle = $state(true);
10+
let items = $state([ 1, 2, 3 ]);
11+
12+
const handle_toggle = async () => {
13+
toggle = false;
14+
await Promise.resolve();
15+
items = [3, 4];
16+
toggle = true;
17+
};
18+
</script>
19+
20+
<button onclick={handle_toggle}>Toggle</button>
21+
22+
{#if toggle}
23+
<div transition:fade>
24+
{#each items as item (item)}
25+
{(() => {
26+
$effect(() => {
27+
items;
28+
console.log('$effect');
29+
});
30+
31+
$effect.pre(() => {
32+
items;
33+
console.log('$effect.pre');
34+
});
35+
})()}
36+
<div>{item}</div>
37+
{/each}
38+
</div>
39+
{/if}

0 commit comments

Comments
 (0)