Svelte Reactive Statements: Practical Guide to Local Derived State
Svelte's $: reactive statements provide compile‑time derived state for local component logic, but they have limits on reuse, heavy work, and side‑effects. Learn when to use them and when a derived store is better.
18 Jul 2026, 04:54 UTC

Problem: Keeping Derived State in Sync
In a Svelte component you often need a value that depends on other reactive variables—for example a formatted string, a computed flag, or a filtered list. Updating that value manually in every event handler quickly becomes verbose and error‑prone. The question is whether to keep the derivation local with a $: reactive statement or to move the logic into a derived store that can be shared.
Thesis
Reactive statements are the lightweight, compiler‑driven way to derive state that lives only inside a single component. They run automatically when their dependencies change, produce minimal runtime overhead, and keep the component self‑contained. However, they are not suitable for heavy computations, for logic that must be reused across components, or for side‑effects that should not run on every update.
How Reactive Statements Work (Svelte 4)
A $: block is a top‑level statement inside the component’s <script>. During compilation Svelte builds a static dependency graph from the variables referenced in the block. The generated code re‑executes the block only when one of those variables changes. This means:
- Dependency tracking is done at compile time; there is no runtime library overhead for tracking.
- The block runs after its dependencies have been updated, in deterministic order.
- No manual subscriptions or event listeners are required.
Note that the dependency graph is frozen after compilation; adding or removing references inside the block requires a recompilation for the graph to update.
Practical Example: Formatted Date
The following component keeps a formatted date string in sync with three number inputs. The reactive statement derives formattedDate without any manual updates.
<label>
Day: <input type="number" bind:value={day} min="1" max="31">
</label>
<label>
Month: <input type="number" bind:value={month} min="1" max="12">
</label>
<label>
Year: <input type="number" bind:value={year} min="1900" max="2100">
</label>
<p>Formatted: {formattedDate}</p>
Changing any input updates formattedDate instantly. If you uncomment the console.log line you will see it fire only when day, month, or year changes, not on unrelated state updates.
Trade‑offs and Limitations
- Performance: For simple expressions the block is cheap. If the block contains a heavy loop, expensive calculation, or asynchronous work, it will re‑run on every dependency change and can block the UI. In such cases consider memoizing the result with a helper function or moving the logic to a derived store that caches the value.
- Reusability: Reactive statements are component‑scoped; they cannot be imported or shared. If the same derived logic is needed in multiple components, a derived store or a plain utility function is a better fit.
- Side‑Effects: Svelte does not emit a compiler warning for non‑idempotent work inside a
$:block. Developers must avoid side‑effects (e.g., network requests, direct DOM mutations) in these blocks; useonMount, a store subscription, or an action instead. - Dependency Graph Static: Because the graph is generated at compile time, altering the set of referenced variables after the fact does not automatically update tracking—recompilation is required.
When to Prefer a Derived Store
Derived stores shine when:
- The derived value must be accessed by more than one component.
- You want to encapsulate complex logic, caching, or asynchronous derivation.
- You need a reusable reactive API that can be imported across modules.
Example of a derived store that formats a date and can be shared:
// dateStore.js
import { writable, derived } from 'svelte/store';
export const day = writable(1);
export const month = writable(1);
export const year = writable(2024);
export const formattedDate = derived(
[day, month, year],
([$day, $month, $year]) => `${$month.toString().padStart(2, '0')}/${$day.toString().padStart(2, '0')}/${$year}`
);
Any component can import formattedDate and bind to day, month, and year as needed, guaranteeing that the derived value stays in sync everywhere.
Actionable Guidance
- Start with a reactive statement for local, simple derived values (e.g., UI flags, formatted strings).
- Verify the dependency set by adding a temporary
console.loginside the block; it should log only when the expected variables change. - Watch for heavy work; if the block does expensive computation, profile with Chrome DevTools Performance panel and consider moving the logic to a derived store or a memoized helper.
- Avoid side‑effects in
$:blocks; use lifecycle functions or stores for asynchronous or non‑idempotent work. - Move to a derived store when the same derivation is needed in multiple components or when you need to share the reactive value across the app.
By following these steps you can keep Svelte components concise, performant, and maintainable while making an informed choice between reactive statements and derived stores.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.