diff --git a/.changeset/rotten-actors-rush.md b/.changeset/rotten-actors-rush.md new file mode 100644 index 0000000000..46f15bdf27 --- /dev/null +++ b/.changeset/rotten-actors-rush.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure unowned derived signals correctly re-connect to graph diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index bd4e387c3d..dc103bbea6 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -196,18 +196,20 @@ export function check_dirtiness(reaction) { update_derived(/** @type {Derived} */ (dependency)); } - if (dependency.version > reaction.version) { - return true; + // If we are working with an unowned signal as part of an effect (due to !current_skip_reaction) + // and the version hasn't changed, we still need to check that this reaction + // is linked to the dependency source – otherwise future updates will not be caught. + if ( + is_unowned && + current_effect !== null && + !current_skip_reaction && + !dependency?.reactions?.includes(reaction) + ) { + (dependency.reactions ??= []).push(reaction); } - if (is_unowned) { - // TODO is there a more logical place to do this work? - if (!current_skip_reaction && !dependency?.reactions?.includes(reaction)) { - // If we are working with an unowned signal as part of an effect (due to !current_skip_reaction) - // and the version hasn't changed, we still need to check that this reaction - // if linked to the dependency source – otherwise future updates will not be caught. - (dependency.reactions ??= []).push(reaction); - } + if (dependency.version > reaction.version) { + return true; } } } diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-9/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-unowned-9/_config.js new file mode 100644 index 0000000000..ce3cfa122f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-9/_config.js @@ -0,0 +1,71 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target, logs }) { + let [btn1, btn2] = target.querySelectorAll('button'); + + btn1.click(); + btn2.click(); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
1
\ndouble:\n2` + ); + + btn1.click(); + btn2.click(); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
2
\ndouble:\n4` + ); + + btn1.click(); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
3
\ndouble:\n6` + ); + + btn1.click(); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
4
\ndouble:\n8` + ); + + btn1.click(); + + flushSync(); + + assert.htmlEqual(target.innerHTML, `
5
`); + + btn1.click(); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
6
\ndouble:\n12` + ); + + btn1.click(); + + flushSync(); + + assert.htmlEqual( + target.innerHTML, + `
7
\ndouble:\n14` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-unowned-9/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-unowned-9/main.svelte new file mode 100644 index 0000000000..6c624e94e0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-unowned-9/main.svelte @@ -0,0 +1,24 @@ + + + + + +
{count}
+ +{#if double && count % 5} + double: {double.v} +{/if}