From bc6921107e2fad08e19c90032dc29fb91c8412ca Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 11 Nov 2025 16:04:29 +0100 Subject: [PATCH] fix: ensure eager effects don't break reactions chain Execution of eager effects didn't set `is_updating_effect`, which meant the logic in `get` would wrongfully prevent dependencies being added to `reactions` of sources/deriveds. Fixes #17133 --- .changeset/silent-teeth-invent.md | 5 +++ .../src/internal/client/reactivity/sources.js | 26 +++++++++----- .../samples/inspect-derived-4/_config.js | 35 +++++++++++++++++++ .../samples/inspect-derived-4/main.svelte | 14 ++++++++ 4 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 .changeset/silent-teeth-invent.md create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-derived-4/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-derived-4/main.svelte diff --git a/.changeset/silent-teeth-invent.md b/.changeset/silent-teeth-invent.md new file mode 100644 index 000000000000..54603cc27e35 --- /dev/null +++ b/.changeset/silent-teeth-invent.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure eager effects don't break reactions chain diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 8ae406b57b30..052ca97dc071 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -14,7 +14,9 @@ import { is_dirty, untracking, is_destroying_effect, - push_reaction_value + push_reaction_value, + set_is_updating_effect, + is_updating_effect } from '../runtime.js'; import { equals, safe_equals } from './equality.js'; import { @@ -246,19 +248,25 @@ export function internal_set(source, value) { export function flush_eager_effects() { eager_effects_deferred = false; + var prev_is_updating_effect = is_updating_effect; + set_is_updating_effect(true); const inspects = Array.from(eager_effects); - for (const effect of inspects) { - // Mark clean inspect-effects as maybe dirty and then check their dirtiness - // instead of just updating the effects - this way we avoid overfiring. - if ((effect.f & CLEAN) !== 0) { - set_signal_status(effect, MAYBE_DIRTY); - } + try { + for (const effect of inspects) { + // Mark clean inspect-effects as maybe dirty and then check their dirtiness + // instead of just updating the effects - this way we avoid overfiring. + if ((effect.f & CLEAN) !== 0) { + set_signal_status(effect, MAYBE_DIRTY); + } - if (is_dirty(effect)) { - update_effect(effect); + if (is_dirty(effect)) { + update_effect(effect); + } } + } finally { + set_is_updating_effect(prev_is_updating_effect); } eager_effects.clear(); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived-4/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-derived-4/_config.js new file mode 100644 index 000000000000..3a3bca7221a3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived-4/_config.js @@ -0,0 +1,35 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; +import { normalise_inspect_logs } from '../../../helpers'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, target, logs }) { + const [b] = target.querySelectorAll('button'); + + b.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ``); + + b.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ``); + + b.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ``); + + assert.deepEqual(normalise_inspect_logs(logs), [ + [0, 1, 2], + [1, 2], + 'at SvelteSet.add', + [2], + 'at SvelteSet.add', + [], + 'at SvelteSet.add' + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-derived-4/main.svelte new file mode 100644 index 000000000000..eb4ea891dbeb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived-4/main.svelte @@ -0,0 +1,14 @@ + + +