From c6af159ec27cc1920d709cf5eba42f03f242f8f6 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 27 Jun 2025 00:10:04 +0200 Subject: [PATCH 1/2] fix: ensure unowned deriveds can add themselves as reactions while connected Fixes #15829 and potentially #15853 --- .changeset/little-kings-smoke.md | 5 +++ .../svelte/src/internal/client/runtime.js | 7 ++- packages/svelte/tests/signals/test.ts | 44 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 .changeset/little-kings-smoke.md diff --git a/.changeset/little-kings-smoke.md b/.changeset/little-kings-smoke.md new file mode 100644 index 000000000000..1e7f82352a7e --- /dev/null +++ b/.changeset/little-kings-smoke.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure unowned deriveds can add themselves as reactions while connected diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 56bc157f33cf..d057bfdf0d9d 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -294,7 +294,12 @@ export function update_reaction(reaction) { reaction.deps = deps = new_deps; } - if (!skip_reaction) { + if ( + !skip_reaction || + // Deriveds that already have reactions can cleanup, so we still add them as reactions + ((flags & DERIVED) !== 0 && + /** @type {import('#client').Derived} */ (reaction).reactions !== null) + ) { for (i = skipped_deps; i < deps.length; i++) { (deps[i].reactions ??= []).push(reaction); } diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 78d7919e0fb1..6c75936e064c 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -112,6 +112,50 @@ describe('signals', () => { }; }); + test('unowned deriveds are not added as reactions but trigger effects', () => { + var obj = state(undefined); + + class C1 { + #v = state(0); + get v() { + return $.get(this.#v); + } + set v(v: number) { + set(this.#v, v); + } + } + + return () => { + console.log('1'); + let d = derived(() => $.get(obj)?.v || '-'); + + const log: number[] = []; + assert.equal($.get(d), '-'); + console.log('2'); + + let destroy = effect_root(() => { + render_effect(() => { + log.push($.get(d)); + }); + }); + + console.log('3'); + set(obj, new C1()); + flushSync(); + console.log('4'); + assert.equal($.get(d), '-'); + $.get(obj)!.v = 1; + flushSync(); + console.log('5'); + assert.equal($.get(d), 1); + assert.deepEqual(log, ['-', 1]); + destroy(); + // ensure we're not leaking reactions + assert.equal(obj.reactions, null); + assert.equal(d.reactions, null); + }; + }); + test('derived from state', () => { const log: number[] = []; From 065690bf0fa464e21327a544b65cacd070726ade Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 27 Jun 2025 00:16:40 +0200 Subject: [PATCH 2/2] lint --- packages/svelte/tests/signals/test.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 6c75936e064c..719c936df002 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -113,7 +113,7 @@ describe('signals', () => { }); test('unowned deriveds are not added as reactions but trigger effects', () => { - var obj = state(undefined); + var obj = state(undefined); class C1 { #v = state(0); @@ -126,12 +126,10 @@ describe('signals', () => { } return () => { - console.log('1'); let d = derived(() => $.get(obj)?.v || '-'); const log: number[] = []; assert.equal($.get(d), '-'); - console.log('2'); let destroy = effect_root(() => { render_effect(() => { @@ -139,14 +137,11 @@ describe('signals', () => { }); }); - console.log('3'); set(obj, new C1()); flushSync(); - console.log('4'); assert.equal($.get(d), '-'); - $.get(obj)!.v = 1; + $.get(obj).v = 1; flushSync(); - console.log('5'); assert.equal($.get(d), 1); assert.deepEqual(log, ['-', 1]); destroy();