docs / Runtime

get and set

Reaching past the binding to the raw cell: get, set, and the reads that deliberately do not subscribe.

With a &[ binding you never need get or set — you read count and write count, and the compiler handles the cell. These are for the cases the binding cannot cover: a helper that was handed the CELL, an API that wants a cell, and reads that must NOT subscribe.

When you have a cell, get and set are explicit

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

  const bump = () => {
    const next = get(countCell) + 1
    countCell.set(next)
  }

  <button onClick={bump}>{count}</button>
}
									
								

Notice that the markup still says {count} and the button still reads as a normal component. Once you have pulled out the cell, only the code that actually holds the cell uses get/set.

The four spellings

Spelling Where it works
count Anywhere — markup, a handler, a helper. The normal way in and out.
count++ / count = v Anywhere. Sugar for the row above; prefer it.
countCell.set(v) Anywhere you hold the CELL — a component body, a handler, a helper called from either.
set(countCell, v) Anywhere you hold the CELL. Same thing, free-function form.
set(count, v) Only inside handlers and JSX, where the compiler rewrites it. As a bare statement in a component body it THROWS at render.

The last row is the one that bites, and it only happens when you mix the two styles. set(count, 5) on its own line in a component body reaches the runtime set unrewritten and takes the page down with 'Cannot read properties of undefined'. Write count = 5 instead — it is shorter and cannot get this wrong.

Untracked reads: peek and untrack

Sometimes you want the CURRENT value without subscribing. peek(cell) reads it once; untrack(fn) runs a function with tracking off. Both are for code that should read state without becoming a reason to re-run.

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

  const bump = () => { count++ }

  // Read count right now, without the log re-rendering when count changes.
  const log = () => console.log('count is', peek(countCell))

  return <button onClick={() => { bump(); log() }}>{count}</button>
}
									
								

An untracked read is a SNAPSHOT. const logged = untrack(() => peek(countCell)) captures the value once, and {logged} in markup will never update — it is not a reactive binding. That is the point, but it means untracked reads belong in an event handler or a log, not in the template. If the template should follow the value, read the binding.

untrack(fn) is the broader form: use it when a helper reads several cells and you only want some of them to subscribe.

app/report/page.vsk
									
										component Report() {
  const &[rows] = track<string[]>([])
  const &[sortKey, sortKeyCell] = track('name')

  // Re-sorts when rows change, but NOT when sortKey changes — sortKey is only
  // read inside untrack, so it does not become a dependency.
  const sorted = derived(() => {
    const key = untrack(() => get(sortKeyCell))
    return [...rows].sort((a, b) => a.localeCompare(b, key))
  })

  <p>sort key: {sortKey}</p>
  <ul>
    for (const row of get(sorted)) {
      <li>{row}</li>
    }
  </ul>
}