Local State

There is no state API to learn.

let is reactive state; const is a plain value. Anything you assign to a let updates the DOM that reads it.

<script>
let name = 'World'
let count = 0
</script>

<p>Hello, {name}! You have clicked {count} times.</p>

<button onclick="count++">Click me</button>
<button onclick="name = 'JAF'">Rename</button>
output

Hello, World! You have clicked 0 times.

Functions are usually const: the function itself never changes, even though it changes reactive variables when it runs.

Derived Values

A let whose initialiser reads other reactive variables is derived: JAF recomputes it whenever any of them change.

Derived values can build on other derived values.

<script>
let price = 10
let quantity = 2

// Derived: recomputed whenever price or quantity changes.
let total = price * quantity
</script>

<p>{quantity} x {price} = {total}</p>

<button onclick="quantity++">One more</button>
output

2 x 10 = 20

Only expressions over reactive variables are derived. A literal, a new Date() or a fetch() call reads no reactive state, so it is ordinary state that keeps whatever you assign it.

Objects and Arrays

Mutating deeply works as well as replacing: user.age++ and todos.push(...) both update the DOM.

<script>
let user = { name: 'Alice', age: 30 }
let todos = []
</script>

<p>{user.name} is {user.age}. Todos: {todos.length}</p>

<button onclick="user.age++">Birthday</button>
<button onclick="todos.push({ text: 'New todo' })">Add a todo</button>
output

Alice is 30. Todos: 0

Side Effects

effect() runs its body once on mount and again whenever anything it read changes.

Effects are torn down with the component, so there is nothing to unsubscribe.

source
<script>
let count = 0

effect(() => {
  console.log('Count changed:', count)
  // Runs on initial mount AND every update
})
</script>