diff --git a/.changeset/little-kings-smoke.md b/.changeset/little-kings-smoke.md new file mode 100644 index 0000000000..1e7f82352a --- /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 56bc157f33..d057bfdf0d 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 78d7919e0f..719c936df0 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -112,6 +112,45 @@ 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 () => { + let d = derived(() => $.get(obj)?.v || '-'); + + const log: number[] = []; + assert.equal($.get(d), '-'); + + let destroy = effect_root(() => { + render_effect(() => { + log.push($.get(d)); + }); + }); + + set(obj, new C1()); + flushSync(); + assert.equal($.get(d), '-'); + $.get(obj).v = 1; + flushSync(); + 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[] = [];