diff --git a/.changeset/nasty-carrots-develop.md b/.changeset/nasty-carrots-develop.md new file mode 100644 index 000000000000..3f793686a64a --- /dev/null +++ b/.changeset/nasty-carrots-develop.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure async initial store value is noticed diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index f1885c7904f3..8fa229d56fb4 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -97,11 +97,7 @@ export function set(source, value) { // reactions as we only allocate and assign the reactions after the signal // has fully executed. So in the case of ensuring it registers the reaction // properly for itself, we need to ensure the current effect actually gets - // scheduled. i.e: - // - // $effect(() => x++) - // - // We additionally want to skip this logic when initialising store sources + // scheduled. i.e: `$effect(() => x++)` if ( is_runes() && current_effect !== null && diff --git a/packages/svelte/src/internal/client/reactivity/store.js b/packages/svelte/src/internal/client/reactivity/store.js index 6c342c4920b3..46e57b0fd072 100644 --- a/packages/svelte/src/internal/client/reactivity/store.js +++ b/packages/svelte/src/internal/client/reactivity/store.js @@ -31,18 +31,19 @@ export function store_get(store, store_name, stores) { set(entry.source, undefined); entry.unsubscribe = noop; } else { - var initial = true; + var is_synchronous_callback = true; entry.unsubscribe = subscribe_to_store(store, (v) => { - if (initial) { - // if the first time the store value is read is inside a derived, - // we will hit the `state_unsafe_mutation` error if we `set` the value + if (is_synchronous_callback) { + // If the first updates to the store value (possibly multiple of them) are synchronously + // inside a derived, we will hit the `state_unsafe_mutation` error if we `set` the value entry.source.v = v; - initial = false; } else { set(entry.source, v); } }); + + is_synchronous_callback = false; } } diff --git a/packages/svelte/tests/runtime-runes/samples/store-async-first-value/_config.js b/packages/svelte/tests/runtime-runes/samples/store-async-first-value/_config.js new file mode 100644 index 000000000000..7afa0abfece3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-async-first-value/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + mode: ['client'], + async test({ assert, target }) { + assert.htmlEqual(target.innerHTML, ' / '); + await new Promise((r) => setTimeout(r, 110)); + assert.htmlEqual(target.innerHTML, '42 / 42'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/store-async-first-value/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-async-first-value/main.svelte new file mode 100644 index 000000000000..e1faec61367d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/store-async-first-value/main.svelte @@ -0,0 +1,19 @@ + + +{$value2} / {derivedValue} diff --git a/packages/svelte/tests/runtime-runes/samples/store-no-mutation-validation/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-no-mutation-validation/main.svelte index 098823e3d89a..36eb1875e099 100644 --- a/packages/svelte/tests/runtime-runes/samples/store-no-mutation-validation/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/store-no-mutation-validation/main.svelte @@ -1,7 +1,18 @@ - -

Hello {name}

+

{hello} {name}