From 286b40c4526ce9970cb81ddd5e65b93b722fe468 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Jan 2026 15:57:22 -0500 Subject: [PATCH] fix: prevent derives without dependencies from ever re-running (#17445) * fix: prevent derives without dependencies from ever re-running * tweak Co-authored-by: David Roizenman --- .changeset/plenty-candies-notice.md | 5 ++++ .../internal/client/reactivity/deriveds.js | 17 ++++++++++---- .../fork-derived-class-instance/_config.js | 22 ++++++++++++++++++ .../fork-derived-class-instance/main.svelte | 23 +++++++++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 .changeset/plenty-candies-notice.md create mode 100644 packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/main.svelte diff --git a/.changeset/plenty-candies-notice.md b/.changeset/plenty-candies-notice.md new file mode 100644 index 0000000000..41c5fce12a --- /dev/null +++ b/.changeset/plenty-candies-notice.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent derives without dependencies from ever re-running diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 0fe83d7108..59fb0f15aa 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -9,7 +9,8 @@ import { STALE_REACTION, ASYNC, WAS_MARKED, - DESTROYED + DESTROYED, + CLEAN } from '#client/constants'; import { active_reaction, @@ -33,7 +34,7 @@ import { UNINITIALIZED } from '../../../constants.js'; import { batch_values, current_batch } from './batch.js'; import { unset_context } from './async.js'; import { deferred } from '../../shared/utils.js'; -import { update_derived_status } from './status.js'; +import { set_signal_status, update_derived_status } from './status.js'; /** @type {Effect | null} */ export let current_async_effect = null; @@ -356,15 +357,21 @@ export function update_derived(derived) { var value = execute_derived(derived); if (!derived.equals(value)) { + derived.wv = increment_write_version(); + // in a fork, we don't update the underlying value, just `batch_values`. // the underlying value will be updated when the fork is committed. // otherwise, the next time we get here after a 'real world' state // change, `derived.equals` may incorrectly return `true` - if (!current_batch?.is_fork) { + if (!current_batch?.is_fork || derived.deps === null) { derived.v = value; - } - derived.wv = increment_write_version(); + // deriveds without dependencies should never be recomputed + if (derived.deps === null) { + set_signal_status(derived, CLEAN); + return; + } + } } // don't mark derived clean if we're reading it inside a diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/_config.js b/packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/_config.js new file mode 100644 index 0000000000..feac447536 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/_config.js @@ -0,0 +1,22 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip_no_async: true, + async test({ assert, target }) { + const [fork] = target.querySelectorAll('button'); + + fork.click(); + await tick(); + + const [, increment] = target.querySelectorAll('button'); + const p = target.querySelector('p'); + + assert.equal(p?.textContent, '0'); + + increment.click(); + await tick(); + + assert.equal(p?.textContent, '1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/main.svelte b/packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/main.svelte new file mode 100644 index 0000000000..3623c1df66 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/fork-derived-class-instance/main.svelte @@ -0,0 +1,23 @@ + + + + +{#if condition} + +

{counter.count}

+{/if}