Stale derived values from non‑deterministic reactive block ordering
0 reputation · 20 May 2024, 22:25 UTC
0 reputation · 20 May 2024, 22:25 UTC
Svelte compiles each reactive block ($:) into a dependency graph, executing them in source order by default. However, the compiler may batch multiple updates into a single tick and reorder blocks that depend on the same variable. This optimization is documented, but the order is not guaranteed when an async event triggers a state change, leading to intermittent stale derived values.
Because developers sometimes chain reactive assignments to derive state, the lack of determinism can result in a component rendering with an old value immediately after an asynchronous fetch and updating to the correct value on the next tick. This intermittent behaviour is hard to reproduce and can be mistaken for a race condition in the application code.
The documentation notes that developers should avoid relying on a specific order in such scenarios, yet there is no tooling to enforce it or to expose the actual execution sequence to the developer.
34775 reputation · 21 May 2024, 00:36 UTC
First, answer the question directly: you can observe the execution order by adding a unique console.log to each $: block, then trigger the asynchronous state change that causes the batch. The order printed in the console shows the compiler‑chosen sequence for that build. If you need a guaranteed order, Svelte 4 provides no flag or annotation to enforce it; the documented guidance is to avoid depending on block order. In Svelte 5, the $derived and $effect runes give a deterministic evaluation order, eliminating the stale‑value window.
The likely cause is that Svelte 4 compiles each $: block into a node of a dependency graph. When two blocks read the same variable but neither writes a variable the other reads, the graph has no edge between them, so their relative order is unspecified. During a tick, Svelte may batch multiple synchronous mutations (e.g., from a Promise.then) and then execute the blocks in an implementation‑defined sequence, allowing a later block to see the pre‑update value of a shared dependency while an earlier block has already updated its derived state.
console.log('block A') in the first $: block and console.log('block B') in the second.Promise.resolve().then(() => { … }) callback so the change is batched.derived(store, fn)) – the store graph enforces a single evaluation order.To give a definitive recommendation, please confirm whether you are using Svelte 4 or Svelte 5.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.