docs / Runtime

track

The reactive primitive: a cell you read, write and subscribe to — with a binding you use like a plain variable.

track(init) creates a CELL: a box holding a value the compiler knows how to re-read. Nothing polls it, nothing diffs the DOM — a write notifies exactly the places that read the cell, and those places re-run. That is why the runtime you ship is small: reactivity is compile-time knowledge, not a subscription graph walked at render time.

You almost never touch the cell itself. The &[ destructuring gives you a BINDING that you read and write like any other variable — count++, count = 5, count === 2 — and the compiler rewrites those into cell reads and writes for you.

app/counter/page.vsk
									
										component Counter() {
  const &[count] = track(0)

  <p>Count: {count}</p>
  <button onClick={() => count++}>increment</button>
}
									
								

The binding is the API

Anything you can do to a variable, you can do to a tracked binding. There is no setter to call and no getter to remember.

You write It means
count read — subscribes whatever expression it appears in
count++ read and write
count = 5 write
count === 2 read — reactive condition
'/api/' + count read — reactive string building

The binding is deep. Once a tracked value holds an object or an array, mutating through it is a write — you do not have to reassign the whole thing.

app/user/page.vsk
									
										component User() {
  const &[user] = track({ name: 'ada', hits: 0 })
  const &[tags] = track<string[]>([])

  const visit = () => {
    user.hits++
    tags.push('visit')
  }

  <button onClick={visit}>{user.name} — {user.hits} hits, {tags.length} tags</button>
}
									
								

When you need the raw cell

The second name in the pattern is the raw cell — an object, not a value. Ask for it when you are handing the CELL to something that cannot see a binding. Skip it otherwise: if you are only reading or writing, the binding is shorter and clearer.

app/counter/page.vsk
									
										component Counter() {
  const &[count, countCell] = track(0)

  const bump = (cell: { get(): number; set(v: number): void }) => cell.set(cell.get() + 1)
  bump(countCell)

  <p>{count}</p>
}
									
								
  • An API that takes a cell — useFetch({ into: cell }), createResource({ into: cell }). This is the main reason the raw cell exists.
  • A helper that receives state as an argument — pass the cell, not the value, so the helper reads the CURRENT value instead of a snapshot taken when it was defined.
  • Storing state in a structure — an array of setters, a map of cells, a callback queue. Each entry has to stay live rather than freeze at the value it held.

Passing the VALUE to a helper is the bug this prevents. const show = () => alert(count) captures count once; const show = () => alert(countCell.get()) always reads the current value.

The second name is NOT a callable setter. setCount(5) throws — there is no setCount. Write count = 5, or countCell.set(5).

Initialising from props

app/counter/page.vsk
									
										component Counter(props: { start: number }) {
  const &[count] = track(props.start)

  <p>Starting at {count}</p>
}
									
								

Many cells in one component

Cells are independent. Writing one never touches another, and only the expressions that read that particular cell re-run.

app/form/page.vsk
									
										component SignupForm() {
  const &[name] = track('')
  const &[agree] = track(false)

  <label>
    <input value={name} onInput={(e) => name = e.target.value} />
  </label>
  <label>
    <input type="checkbox" checked={agree} onChange={() => agree = !agree} />
  </label>
  <button disabled={!agree || name === ''}>Continue</button>
}
									
								

A cell can hold anything

app/shop/page.vsk
									
										component Shop() {
  const &[cart] = track<string[]>([])

  const add = (sku: string) => cart.push(sku)

  <button onClick={() => add('vesk-ts')}>Add</button>
  <p>{cart.length} in cart</p>
}
									
								

When to reach for something else

  • A value that should be computed, not stored: derived.
  • Work that should happen when a value changes: effect.
  • A value derived from props that never changes at runtime: a plain const is fine, and cheaper.
  • Reading state without subscribing: peek / untrack — see get and set.