From a224dd58671eb11f9b21e2a1c9f8fc48c0ece63a Mon Sep 17 00:00:00 2001 From: Yuuki Oohara Date: Sat, 15 Aug 2026 01:07:16 +0900 Subject: [PATCH] fix: give effect teardowns the value from before the first write in a flush (#18620) Fixes #18619 Make sure to not override old values with newer values that are part of the same flush --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/lucky-pears-smoke.md | 5 +++++ .../src/internal/client/reactivity/batch.js | 1 + .../src/internal/client/reactivity/sources.js | 8 ++++++- .../_config.js | 17 +++++++++++++++ .../main.svelte | 21 +++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .changeset/lucky-pears-smoke.md create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte diff --git a/.changeset/lucky-pears-smoke.md b/.changeset/lucky-pears-smoke.md new file mode 100644 index 0000000000..816a37dca5 --- /dev/null +++ b/.changeset/lucky-pears-smoke.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: give effect teardowns the value from before the first write in a flush diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 5becae2dc9..8251f79298 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -420,6 +420,7 @@ export class Batch { } if (next_batch !== null) { + old_values.clear(); next_batch.#process(); } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 218941ab67..70be6489a4 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -180,7 +180,13 @@ export function set(source, value, should_proxy = false) { */ export function internal_set(source, value, updated_during_traversal = null) { if (!source.equals(value)) { - old_values.set(source, is_destroying_effect ? value : source.v); + if (is_destroying_effect) { + old_values.set(source, value); + } else if (!old_values.has(source)) { + // only record the value from before the first write in this flush, otherwise a + // teardown would see the value from before whichever write happened to be last + old_values.set(source, source.v); + } var batch = Batch.ensure(); batch.capture(source, value); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js new file mode 100644 index 0000000000..7db4966106 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + + assert.deepEqual(logs, [ + 'register: one', + 'unregister: one', + 'leftover: none', + 'register: three' + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte new file mode 100644 index 0000000000..a3932aaa22 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-multiple-writes/main.svelte @@ -0,0 +1,21 @@ + + +