From 3608b3c8691cf855ec2cdeb623b904d2b95eddcc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 28 Jan 2026 16:59:40 -0500 Subject: [PATCH] fix: preserve old dependencies when updating reaction inside fork (#17579) Co-authored-by: David Roizenman --- .changeset/fuzzy-spies-love.md | 5 ++++ .../svelte/src/internal/client/runtime.js | 18 ++++++++++--- .../_config.js | 25 +++++++++++++++++ .../main.svelte | 27 +++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 .changeset/fuzzy-spies-love.md create mode 100644 packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/main.svelte diff --git a/.changeset/fuzzy-spies-love.md b/.changeset/fuzzy-spies-love.md new file mode 100644 index 0000000000..9129d986bc --- /dev/null +++ b/.changeset/fuzzy-spies-love.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: preserve old dependencies when updating reaction inside fork diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2075081f96..70eeabb789 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -43,7 +43,13 @@ import { set_dev_current_component_function, set_dev_stack } from './context.js'; -import { Batch, batch_values, flushSync, schedule_effect } from './reactivity/batch.js'; +import { + Batch, + batch_values, + current_batch, + flushSync, + schedule_effect +} from './reactivity/batch.js'; import { handle_error } from './error-handling.js'; import { UNINITIALIZED } from '../../constants.js'; import { captured_signals } from './legacy.js'; @@ -249,10 +255,16 @@ export function update_reaction(reaction) { var result = fn(); var deps = reaction.deps; + // Don't remove reactions during fork; + // they must remain for when fork is discarded + var is_fork = current_batch?.is_fork; + if (new_deps !== null) { var i; - remove_reactions(reaction, skipped_deps); + if (!is_fork) { + remove_reactions(reaction, skipped_deps); + } if (deps !== null && skipped_deps > 0) { deps.length = skipped_deps + new_deps.length; @@ -268,7 +280,7 @@ export function update_reaction(reaction) { (deps[i].reactions ??= []).push(reaction); } } - } else if (deps !== null && skipped_deps < deps.length) { + } else if (!is_fork && deps !== null && skipped_deps < deps.length) { remove_reactions(reaction, skipped_deps); deps.length = skipped_deps; } diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/_config.js b/packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/_config.js new file mode 100644 index 0000000000..c5db69f7d4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/_config.js @@ -0,0 +1,25 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip_no_async: true, + async test({ assert, target }) { + const [fork_btn, _toggle_btn, inc_count_1_btn] = target.querySelectorAll('button'); + const p = /** @type {HTMLElement} */ (target.querySelector('p')); + + assert.equal(p.textContent, '0'); + + // Trigger derived to re-evaluate during fork and switch to tracking count_2 + flushSync(() => { + fork_btn.click(); + }); + + assert.equal(p.textContent, '0'); + + flushSync(() => { + inc_count_1_btn.click(); + }); + + assert.equal(p.textContent, '1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/main.svelte b/packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/main.svelte new file mode 100644 index 0000000000..800c82b56b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-dependency-rollback/main.svelte @@ -0,0 +1,27 @@ + + + +{#if count} +{/if} + + + + + + + +

{count}