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
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.
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.
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>
}