From 27c90dfa8322c9f225e22a730a776c4f4cbd2c5e Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Sat, 19 Jul 2025 08:23:27 +0200 Subject: [PATCH] fix: don't reexecute derived with no dependencies on teardown (#16438) The prior logic was wrong because it reexecuted when something was clean, but we want to when it's not. The remaining fix was to also check the reactions: If an effect is destroyed and it was the last reaction of a derived then the derived is set to `MAYBE_DIRTY`. We therefore also need to check if the derived still has anyone listening to it, and only then reexecute it. Fixes #16363 --- .changeset/clever-toys-decide.md | 5 ++++ .../svelte/src/internal/client/runtime.js | 8 +++-- .../props-derived-teardown/Teardown.svelte | 11 +++++++ .../samples/props-derived-teardown/_config.js | 30 +++++++++++++++++++ .../props-derived-teardown/main.svelte | 25 ++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 .changeset/clever-toys-decide.md create mode 100644 packages/svelte/tests/runtime-runes/samples/props-derived-teardown/Teardown.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/props-derived-teardown/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/props-derived-teardown/main.svelte diff --git a/.changeset/clever-toys-decide.md b/.changeset/clever-toys-decide.md new file mode 100644 index 0000000000..57eb2b0058 --- /dev/null +++ b/.changeset/clever-toys-decide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't reexecute derived with no dependencies on teardown diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 306b9b9dd9..3d3c89975c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -653,8 +653,12 @@ export function get(signal) { var value = derived.v; - // if the derived is dirty, or depends on the values that just changed, re-execute - if ((derived.f & CLEAN) !== 0 || depends_on_old_values(derived)) { + // if the derived is dirty and has reactions, or depends on the values that just changed, re-execute + // (a derived can be maybe_dirty due to the effect destroy removing its last reaction) + if ( + ((derived.f & CLEAN) === 0 && derived.reactions !== null) || + depends_on_old_values(derived) + ) { value = execute_derived(derived); } diff --git a/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/Teardown.svelte b/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/Teardown.svelte new file mode 100644 index 0000000000..f71890d31e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/Teardown.svelte @@ -0,0 +1,11 @@ + + +
teardown
\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/_config.js b/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/_config.js new file mode 100644 index 0000000000..c4e7d1307b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/_config.js @@ -0,0 +1,30 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + assert.htmlEqual( + target.innerHTML, + ` + +
teardown
+
1
+
2
+
3
+ ` + ); + const [increment] = target.querySelectorAll('button'); + + increment.click(); + flushSync(); + + assert.htmlEqual( + target.innerHTML, + ` + +
1
+
3
+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/main.svelte new file mode 100644 index 0000000000..ff9e19b2c6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-derived-teardown/main.svelte @@ -0,0 +1,25 @@ + + + + +{#if show} + +{/if} +{#each test.ids as id} +
{id}
+{/each}