InterviewsVector

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 update

Which to use

SituationUse
Svelte 5 component / shared app state$state (mutate nested directly)
Framework-agnostic or Svelte 4 statewritable store, update immutably
Huge array/object you replace wholesale$state.raw

Common traps

  • Mutating a store's value in place$userData[0].age = 30 on a store's raw value can miss updates; go through update() with a new reference.
  • Manual subscribe without unsubscribe — a memory leak; use $store auto-subscription instead.
  • Spreading with $state — unnecessary; direct mutation is the point of the proxy.

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.

By Mohammad Wasi

Software Engineering Leader & Technical Author · Updated August 26, 2026


Related Posts