diff --git a/.changeset/few-beans-bow.md b/.changeset/few-beans-bow.md new file mode 100644 index 0000000000..5c17c242a8 --- /dev/null +++ b/.changeset/few-beans-bow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: lazily connect derievds (in deriveds) to their parent diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 7ec1ed30bd..da5a75eb6e 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -68,10 +68,6 @@ export function derived(fn) { signal.created = get_stack('CreatedAt'); } - if (parent_derived !== null) { - (parent_derived.children ??= []).push(signal); - } - return signal; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 1d695e1fee..a002665b51 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -957,26 +957,30 @@ export function get(signal) { new_deps.push(signal); } } - } else if (is_derived && /** @type {Derived} */ (signal).deps === null) { + } + + if ( + is_derived && + /** @type {Derived} */ (signal).deps === null && + (active_reaction === null || untracking || (active_reaction.f & DERIVED) !== 0) + ) { var derived = /** @type {Derived} */ (signal); var parent = derived.parent; - var target = derived; - while (parent !== null) { - // Attach the derived to the nearest parent effect, if there are deriveds - // in between then we also need to attach them too + if (parent !== null) { + // Attach the derived to the nearest parent effect or derived if ((parent.f & DERIVED) !== 0) { var parent_derived = /** @type {Derived} */ (parent); - target = parent_derived; - parent = parent_derived.parent; + if (!parent_derived.children?.includes(derived)) { + (parent_derived.children ??= []).push(derived); + } } else { var parent_effect = /** @type {Effect} */ (parent); - if (!parent_effect.deriveds?.includes(target)) { - (parent_effect.deriveds ??= []).push(target); + if (!parent_effect.deriveds?.includes(derived)) { + (parent_effect.deriveds ??= []).push(derived); } - break; } } } diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index e147fd1d0d..5f0b93e136 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -803,6 +803,33 @@ describe('signals', () => { }; }); + test('nested deriveds do not connect inside parent deriveds if unused', () => { + return () => { + let a = render_effect(() => {}); + let b: Derived | undefined; + + const destroy = effect_root(() => { + a = render_effect(() => { + $.untrack(() => { + b = derived(() => { + derived(() => {}); + derived(() => {}); + derived(() => {}); + }); + $.get(b); + }); + }); + }); + + assert.deepEqual(a.deriveds?.length, 1); + assert.deepEqual(b?.children, null); + + destroy(); + + assert.deepEqual(a.deriveds, null); + }; + }); + test('deriveds containing effects work correctly when used with untrack', () => { return () => { let a = render_effect(() => {});