Skip to content

fix: ensure async initial store value is noticed #12486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nasty-carrots-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: ensure async initial store value is noticed
6 changes: 1 addition & 5 deletions packages/svelte/src/internal/client/reactivity/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
11 changes: 6 additions & 5 deletions packages/svelte/src/internal/client/reactivity/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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');
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script>
function store() {
return {
subscribe: (cb) => {
setTimeout(() => {
cb(42);
}, 100);

return () => {};
}
};
}

const value1 = store();
const value2 = store();
const derivedValue = $derived($value1);
</script>

{$value2} / {derivedValue}
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
<script>
import { writable } from "svelte/store";
let store = writable('store');
let name = $derived($store); // store signal is updated during reading this, which normally errors, but shouldn't for stores
<script>
import { writable } from 'svelte/store';

let store1 = writable('store');
let store2 = {
subscribe: (cb) => {
cb('...');
cb('Hello');
return () => {};
}
};

// store signal is updated during reading this, which normally errors, but shouldn't for stores
let name = $derived($store1);
let hello = $derived($store2);
</script>

<h1>Hello {name}</h1>
<h1>{hello} {name}</h1>
Loading