Updating Complex/Nested State in Svelte: Stores (immutably) vs Svelte 5 $state
Quick answer
With a Svelte writable store, update nested data with store.update() and return a NEW object instead of mutating in place, so reactivity fires: store.update(s => ({ ...s, user: { ...s.user, name } })). In Svelte 5, prefer the $state rune — it is deeply reactive via a proxy, so you can mutate nested properties directly (state.user.name = 'X') and the UI updates. Use $state.raw for large objects you always replace wholesale.
Short answer: With a writable store, update nested data with store.update() and return a new object (s => ({ ...s, user: { ...s.user, name } })) — immutability is what keeps reactivity. In Svelte 5, prefer $state: it's deeply reactive, so you can mutate nested properties directly.
Updating deeply-nested state is a common Svelte question, and the right answer now depends on whether you're using a store or Svelte 5's $state rune.
The store way: update immutably
A writable store fires subscribers when its value changes by reference, so return a new object/array rather than mutating in place:
// stores.js
import { writable } from "svelte/store"
export const userData = writable([
{ id: 1, name: "John Doe", age: 25 },
{ id: 2, name: "Jane Smith", age: 30 },
])import { userData } from "./stores"
function updateUserAge(id, age) {
userData.update((users) =>
users.map((u) => (u.id === id ? { ...u, age } : u))
)
}map returns a new array, and the spread returns a new object for the changed row — Svelte sees fresh references and re-renders.
In a component, use auto-subscription
Don't hand-roll subscribe/unsubscribe. The $ prefix subscribes and cleans up automatically:
<script>
import { userData } from "./stores"
</script>
{#each $userData as user (user.id)}
<p>{user.name}: {user.age}</p>
{/each}The Svelte 5 way: $state is deeply reactive
$state wraps objects/arrays in a proxy, so mutating nested properties directly just works — no immutable spread required:
<script>
let users = $state([
{ id: 1, name: "John Doe", age: 25 },
{ id: 2, name: "Jane Smith", age: 30 },
])
function updateUserAge(id, age) {
const user = users.find((u) => u.id === id)
if (user) user.age = age // direct mutation — reactivity still fires
}
</script>
{#each users as user (user.id)}
<p>{user.name}: {user.age}</p>
{/each}For large collections you always replace (not mutate), use $state.raw to skip the proxy overhead:
let rows = $state.raw(bigArray) // reassign rows = [...] to updateWhich to use
| Situation | Use |
|---|---|
| Svelte 5 component / shared app state | $state (mutate nested directly) |
| Framework-agnostic or Svelte 4 state | writable store, update immutably |
| Huge array/object you replace wholesale | $state.raw |
Common traps
- Mutating a store's value in place —
$userData[0].age = 30on a store's raw value can miss updates; go throughupdate()with a new reference. - Manual subscribe without unsubscribe — a memory leak; use
$storeauto-subscription instead. - Spreading with
$state— unnecessary; direct mutation is the point of the proxy.
Related guides
Sources
Key takeaways
- •Store update(): return a NEW object/array; mutating the old value in place may not trigger reactivity.
- •Immutably update one item with map(): users.map(u => u.id === id ? { ...u, age } : u).
- •Prefer $store auto-subscription in components — no manual subscribe()/unsubscribe() boilerplate.
- •Svelte 5 $state is deeply reactive (proxy) — mutate nested props directly; no spread needed.
- •Use $state.raw for large arrays/objects you replace rather than mutate, to skip proxy overhead.
Frequently asked questions
How do I update nested data in a Svelte store?
Use the store's update() method and return a new object, spreading at each level you change: store.update(s => ({ ...s, user: { ...s.user, name } })). Returning a fresh reference is what tells Svelte the value changed.
Do I have to update stores immutably?
For writable stores, yes in practice — mutating the existing value in place and calling set()/update() with the same reference can miss updates. Return a new object/array. In Svelte 5, the $state rune is deeply reactive, so direct mutation of nested properties works without the immutable spread.
Should I use a store or $state in Svelte 5?
Use $state for component and shared reactive state in Svelte 5 — it is simpler and deeply reactive. Stores are still useful for framework-agnostic state, RxJS-style composition, or code shared with Svelte 4. Both are valid; $state is the modern default for most app state.
Software Engineering Leader & Technical Author · Updated August 26, 2026