diff --git a/.changeset/rich-turtles-learn.md b/.changeset/rich-turtles-learn.md new file mode 100644 index 000000000000..99f87ecb1712 --- /dev/null +++ b/.changeset/rich-turtles-learn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure derived reactions have their version kept in sync with the reactive graph diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 795417cc0fdb..4cda2564ebc3 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -1,4 +1,4 @@ -/** @import { Derived, Effect } from '#client' */ +/** @import { Derived, Effect, Value } from '#client' */ import { DEV } from 'esm-env'; import { CLEAN, DERIVED, DIRTY, EFFECT_HAS_DERIVED, MAYBE_DIRTY, UNOWNED } from '../constants.js'; import { @@ -151,6 +151,25 @@ function execute_derived(derived) { return value; } +/** + * @param {Value} value + */ +function sync_reaction_versions(value) { + var reactions = value.reactions; + + if (reactions !== null) { + for (var i = 0; i < reactions.length; i++) { + var reaction = reactions[i]; + if ((value.f & UNOWNED) === 0 && value.wv > reaction.wv) { + reaction.wv = value.wv; + } + if ((reaction.f & DERIVED) !== 0) { + sync_reaction_versions(/** @type {Derived} */ (reaction)); + } + } + } +} + /** * @param {Derived} derived * @returns {void} @@ -165,5 +184,7 @@ export function update_derived(derived) { if (!derived.equals(value)) { derived.v = value; derived.wv = increment_write_version(); + } else { + sync_reaction_versions(derived); } } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 486c819f3671..d8f6291aedfa 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -206,7 +206,7 @@ export function check_dirtiness(reaction) { update_derived(/** @type {Derived} */ (dependency)); } - if (dependency.wv > reaction.wv) { + if (dependency.wv > reaction.wv || (reaction.f & DIRTY) !== 0) { return true; } } diff --git a/packages/svelte/tests/runtime-runes/samples/derived-version-sync/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/_config.js new file mode 100644 index 000000000000..30012f4d0303 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/_config.js @@ -0,0 +1,28 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + let [btn1] = target.querySelectorAll('button'); + + btn1?.click(); + flushSync(); + + btn1?.click(); + flushSync(); + + logs.length = 0; + + btn1?.click(); + flushSync(); + + assert.deepEqual(logs, ['a', { value: 1 }]); + + logs.length = 0; + + btn1?.click(); + flushSync(); + + assert.deepEqual(logs, ['a', { value: 2 }, 'b', { a: 2 }, 'c', { b: 2 }]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/derived-version-sync/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/main.svelte new file mode 100644 index 000000000000..8450d2f16ec3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/derived-version-sync/main.svelte @@ -0,0 +1,28 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte index 1e449198beb3..b4db23942717 100644 --- a/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/derived-write-read-write-read/main.svelte @@ -10,4 +10,4 @@ // resulting in the same value should not prevent pending render effects from updating z; y = 0; -}}>{z} \ No newline at end of file +}}>{z}