state · watch · derived
The reactive primitives. All available from @place/component as the canonical import root — they're re-exports from @place/reactivity, which apps can also import directly if they prefer that scope.
state()
import { state } from '@place/component'
const count = state(0)
count() // 0 (tracks if inside a reactive context)
count.set(1) // direct write
count.update((c) => c + 1) // functional updater
// Pass a function for lazy init — runs once, on first read.
const heavy = state(() => computeExpensiveDefault())
state(initial) returns a callable cell — call it to read (the canonical form), and write with .set(value) or .update(prev => next). Reading inside a tracking context subscribes that context to writes. .read() / .write() are back-compat aliases for the callable read and .set / .update; prefer the canonical forms in new code.
watch()
import { state, watch } from '@place/component'
const a = state(2)
const b = state(3)
const dispose = watch(() => {
console.log('sum =', a() + b())
})
a.set(5) // logs "sum = 8"
dispose() // stops watching
Reactive effect. Re-runs on every change to any read state. The first call always runs. Returns a disposer; watch inside a component scope auto-disposes when the component unmounts.
watch that reads x and writes x creates a self-trigger. The framework guards against immediate infinite recursion, but the watch will re-fire on each external change in a loop. Wrap writes in untrack(...) when the read is incidental (e.g. appending to a log), or split the effect into two watches.derived()
import { derived, state } from '@place/component'
const a = state(2)
const b = state(3)
const c = derived(() => a() + b())
c() // 5 (computed)
c() // 5 (cached — no recomputation)
a.set(10)
c() // 13 (recomputed once)
Memoized accessor. derived(fn) returns () => T that caches its last result; the body recomputes only when one of its tracked sources changes value. Read it many times in one render pass — only the first call computes.
const sum = () => a() + b() — reactive at the read site, just not memoized. Reach for derived when caching matters..peek()
.peek() is a method on every state and derived cell — not a standalone import. It returns the current value without subscribing the active watch / derived. Reach for it when you need a value's current snapshot but don't want a dependency edge (logging, one-shot reads inside an effect).
// .peek() is a METHOD on every state / derived cell — there is no
// standalone peek import. It reads the current value WITHOUT
// subscribing the surrounding watch / derived.
watch(() => {
const tracked = a() // subscribes — watch re-runs when a changes
const v = b.peek() // reads b WITHOUT subscribing
console.log('a =', tracked, 'b (snapshot) =', v)
})
untrack()
import { untrack } from '@place/component'
watch(() => {
const tracked = a()
const ignored = untrack(() => b()) // doesn't add b as a dep
})
batch()
import { batch } from '@place/component'
batch(() => {
a.set(10)
b.set(20)
}) // watchers run once, after the batch
flush()
import { flush } from '@place/component'
// Pending reactive updates run microtask-deferred by default. flush()
// drains the queue synchronously — useful in tests and in DOM-read
// after-write scenarios.
a.set(5)
flush()
// every watcher of a has now re-run
See also
- Concepts: reactivity — the model + live demo
- island · Show · Suspense · Form