diff --git a/.changeset/warm-trams-smash.md b/.changeset/warm-trams-smash.md new file mode 100644 index 0000000000..b9053e2617 --- /dev/null +++ b/.changeset/warm-trams-smash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent flushed effects from running again diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 31408ee8f9..f2643e0c34 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -248,6 +248,7 @@ export function run(thunks) { promise.finally(() => { blocker.settled = true; + unset_context(); }); for (const fn of thunks.slice(1)) { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 3832438f47..edaac0c37c 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -1,6 +1,5 @@ /** @import { Fork } from 'svelte' */ /** @import { Derived, Effect, Reaction, Source, Value } from '#client' */ -/** @import { Boundary } from '../dom/blocks/boundary' */ import { BLOCK_EFFECT, BRANCH_EFFECT, @@ -14,7 +13,6 @@ import { ROOT_EFFECT, MAYBE_DIRTY, DERIVED, - BOUNDARY_EFFECT, EAGER_EFFECT, HEAD_EFFECT, ERROR_VALUE, @@ -225,6 +223,10 @@ export class Batch { flush_queued_effects(render_effects); flush_queued_effects(effects); + // Clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches. + this.#dirty_effects.clear(); + this.#maybe_dirty_effects.clear(); + previous_batch = null; this.#deferred?.resolve(); @@ -423,6 +425,7 @@ export class Batch { batch_values = previous_batch_values; } + this.#skipped_branches.clear(); batches.delete(this); } diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-nav-race/_config.js b/packages/svelte/tests/runtime-runes/samples/async-boundary-nav-race/_config.js new file mode 100644 index 0000000000..940299618e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-nav-race/_config.js @@ -0,0 +1,82 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + + + + +

pending a

+ `, + async test({ assert, target }) { + const [a, b, resolve_a, resolve_b] = target.querySelectorAll('button'); + + resolve_a.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + +

page a

+ ` + ); + + b.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + +

pending b

+ ` + ); + + a.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + +

pending a

+ ` + ); + + resolve_b.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + +

pending a

+ ` + ); + + resolve_a.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + + + +

page a

+ ` + ); + + await new Promise((r) => setTimeout(r, 100)); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-boundary-nav-race/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-boundary-nav-race/main.svelte new file mode 100644 index 0000000000..a6554f5aa6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-boundary-nav-race/main.svelte @@ -0,0 +1,52 @@ + + + + + + + +{#snippet snippet_a()} + + {@const _a = await gate('a')} +

page a

+ + {#snippet pending()} +

pending a

+ {/snippet} +
+{/snippet} + +{#snippet snippet_b()} + + {@const _b = await gate('b')} +

page b

+ + {#snippet pending()} +

pending b

+ {/snippet} +
+{/snippet} + +{@render to_render()}