Lesson 4 / 25

$state

Declare reactive values, objects and arrays.

Reactive variables and deep proxies

let count = $state(0) declares a reactive variable; assigning to it updates everything that reads it. Objects and arrays passed to $state become deeply reactive proxies, so todos.push(item) or user.name = "x" also trigger updates. $state.raw skips the proxy: the value only updates when you reassign it, which is cheaper for large data you replace wholesale. $state.snapshot(value) returns a plain, non-proxied copy, for example before passing it to structuredClone or a library. Class fields can be $state too. In Svelte 4, any top-level let in a component was reactive on assignment and array mutations needed a reassignment such as todos = [...todos, item].

Explicit, fine-grained reactivity

Runes are compiler keywords that declare reactive state, values derived from it and side effects.

Three ideas: $state, $derived, $effect and when to avoid it.
Figure 2.1 — State feeds derived values, which feed effects and the DOM.

Primitive, deep and raw state

Todo list state.

<script lang="ts">
  type Todo = { id: number; text: string; done: boolean };

  let text = $state('');
  let todos = $state<Todo[]>([]);           // deep proxy
  let results = $state.raw<string[]>([]);   // reassign to update

  function add() {
    todos.push({ id: Date.now(), text, done: false }); // mutation is tracked
    text = '';
  }

  function log() {
    console.log($state.snapshot(todos)); // plain array, not a proxy
  }
</script>

<input bind:value={text} />
<button onclick={add} disabled={!text}>Add</button>
<ul>
  {#each todos as todo (todo.id)}
    <li><input type="checkbox" bind:checked={todo.done} /> {todo.text}</li>
  {/each}
</ul>

Destructuring breaks the link

let { done } = todo copies the current value; later reads of done are not reactive. Read todo.done where you need it to stay live.

Quick check: What is the main difference between $state and $state.raw for an array?

  • $state.raw is not reactive at all
  • $state.raw only updates on reassignment, not on mutation
  • $state.raw makes the array deeply reactive
  • $state.raw can only hold strings
Answer

$state.raw only updates on reassignment, not on mutation — Raw state skips the deep proxy.