Preact quick start
@zhuangtai-js/preact fits lightweight Preact components that subscribe to ZhuàngTài atoms and computeds and update directly.
Requirements and install
Section titled “Requirements and install”@zhuangtai-js/core^0.5.0- Preact >=10.9 <11
pnpm add @zhuangtai-js/core @zhuangtai-js/preact preactMinimal counter
Section titled “Minimal counter”Keep shared state in a plain TypeScript module and let the Preact adapter own subscriptions. The updater below returns a new object and array instead of mutating the previous value:
import { atom, computed } from "@zhuangtai-js/core";
export type CounterState = { count: number; history: number[];};
export const counterAtom = atom<CounterState>({ count: 0, history: [] });export const doubleAtom = computed(() => counterAtom.get().count * 2);
export function incrementCounter(state: CounterState): CounterState { const count = state.count + 1; return { ...state, count, history: [...state.history, count] };}import { useAtom, useAtomValue, useSetAtom } from "@zhuangtai-js/preact";import { counterAtom, doubleAtom, incrementCounter } from "../state/counter";
export function Counter() { const [counter, setCounter] = useAtom(counterAtom); const double = useAtomValue(doubleAtom); const reset = useSetAtom(counterAtom);
return ( <section> <button type="button" onClick={() => setCounter(incrementCounter)}> {counter.count} × 2 = {double} </button> <button type="button" onClick={() => reset({ count: 0, history: [] })}> reset ({counter.history.length}) </button> </section> );}Place the state module
Section titled “Place the state module”Put atom, computed, and the updater in src/state/ or src/features/<feature>/state.ts; components should import state and render it. Module-level atoms suit browser-shared state. On the server, call a state factory for every request instead of reusing a mutable atom across requests.
Choose read and write access
Section titled “Choose read and write access”- Read-write:
useAtom(counterAtom)returns[value, setter]. - Read-only:
useAtomValue(doubleAtom)subscribes to any readable atom, including acomputed. - Setter-only:
useSetAtom(counterAtom)returns a stable setter without subscribing to the value.
createAtomHook and createComputedHook bind fixed atoms into argument-free hooks; they do not introduce another state model.
Lifecycle and SSR boundary
Section titled “Lifecycle and SSR boundary”The adapter uses native Preact hooks and useSyncExternalStore from preact/compat. Component unmounts clean up subscriptions, and computed snapshots are cached so a fresh object on every read does not cause a loop. Core still owns immediate set, synchronous watch, Object.is equality, and immutable reference boundaries.
Server rendering uses a browser-independent snapshot reader and does not create a client subscription. Hydration and request isolation remain application responsibilities: create independent user or request state for every SSR request and keep the client initial value aligned with the server output.
Persistence
Section titled “Persistence”When state must survive a reload, see the Persist reference and compose @zhuangtai-js/persist in the state creator. The adapter only handles Preact subscriptions; keep storage and hydration in the state module.
API reference
Section titled “API reference”useAtomValue: read-only subscription to anAtomorcomputed.useSetAtom: a setter that does not subscribe to the value.useAtom: read-write access.createAtomHookandcreateComputedHook: argument-free factories for fixed atoms.
See the Preact reference for complete signatures, snapshots, and SSR semantics.
Next steps
Section titled “Next steps”- Core Concepts: learn the synchronous state primitives.
- Framework adapter chooser: compare read/write and lifecycle APIs.
- Persist reference: configure storage, hydration, and lifecycle controls.