place

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()

ts
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
ts
// 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()

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

Don't write to read state inside watch
A 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()

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

When NOT to derive
For one-shot computations or simple JSX expressions, a plain function works: 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).

ts
// .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()

ts
import { untrack } from '@place/component' watch(() => { const tracked = a() const ignored = untrack(() => b()) // doesn't add b as a dep })

batch()

ts
import { batch } from '@place/component' batch(() => { a.set(10) b.set(20) }) // watchers run once, after the batch

flush()

ts
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