Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sixty-glasses-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: avoid other batches running with queued root effects of main batch
9 changes: 8 additions & 1 deletion packages/svelte/src/internal/client/reactivity/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ export class Batch {

for (const root of root_effects) {
this.#traverse_effect_tree(root, target);
// Note: #traverse_effect_tree runs block effects eagerly, which can schedule effects,
// which means queued_root_effects now may be filled again.
}

if (!this.is_fork) {
Expand Down Expand Up @@ -392,6 +394,10 @@ export class Batch {
// Re-run async/block effects that depend on distinct values changed in both batches
const others = [...batch.current.keys()].filter((s) => !this.current.has(s));
if (others.length > 0) {
// Avoid running queued root effects on the wrong branch
var prev_queued_root_effects = queued_root_effects;
queued_root_effects = [];

/** @type {Set<Value>} */
const marked = new Set();
/** @type {Map<Reaction, boolean>} */
Expand All @@ -410,9 +416,10 @@ export class Batch {

// TODO do we need to do anything with `target`? defer block effects?

queued_root_effects = [];
batch.deactivate();
}

queued_root_effects = prev_queued_root_effects;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
let open = $state(false);
let menuOptionsEl = $state(null);
</script>

<button onclick={() => open = !open}>
toggle

{#if open}
<!-- bind:this uses effect, which is scheduled, causing queued_root_effects to be filled again -->
<span bind:this={menuOptionsEl}>
A
</span>
{/if}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
B
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { tick } from 'svelte';
import { test } from '../../test';

export default test({
async test({ assert, target }) {
const [fork, commit, toggle] = target.querySelectorAll('button');

fork.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>commit</button>
<button>toggle</button>
`
);

toggle.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>commit</button>
<button>toggle <span>A</span></button>
`
);

toggle.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>commit</button>
<button>toggle</button>
`
);

toggle.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>commit</button>
<button>toggle <span>A</span></button>
`
);

commit.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>fork</button>
<button>commit</button>
B
`
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import { fork } from 'svelte';
import A from './A.svelte';
import B from './B.svelte';

let open = $state(true);
let f;
</script>


<button onclick={() => {
f = fork(() => {
open = !open;
})
}}>fork</button>
<button onclick={() => {
f.commit()
}}>commit</button>

{#if open}
<A />
{:else}
<B />
{/if}
Loading