Skip to content

React quick start

@zhuangtai-js/react fits React components that need to read, write, and automatically clean up ZhuàngTài atom subscriptions.

  • @zhuangtai-js/core ^0.5.0
  • React >=18 <20
Terminal window
pnpm add @zhuangtai-js/core @zhuangtai-js/react react

Keep state and derived values in a plain TypeScript module, then use the React adapter at the component boundary. This example uses object spread and a new array for an immutable update:

src/state/counter.ts
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] };
}
src/components/Counter.tsx
import { useAtom, useAtomValue, useSetAtom } from "@zhuangtai-js/react";
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>
);
}

Put atom, computed, and typed updaters in src/state/ or src/features/<feature>/state.ts; keep components focused on rendering and events. Module-level atoms are fine for client-shared state. For server request state, call a state factory once per request so mutable references are not shared across requests.

  • Read-write: useAtom(counterAtom) returns [value, setter] for the counter above.
  • Read-only: useAtomValue(doubleAtom) subscribes to an Atom or computed for derived display values.
  • Setter-only: useSetAtom(counterAtom) returns a stable setter without subscribing, which suits reset or command buttons.

No Provider is required; components share state by importing the same atom reference.

The adapter uses React’s useSyncExternalStore to bridge Core’s synchronous get() and watch(), and it unsubscribes when the component unmounts. Core still owns immediate set, synchronous watchers, and Object.is equality; React may delay DOM commits, but the adapter adds no batching or hidden scheduling.

SSR uses get() as the server snapshot, while hydration, request isolation, and the server state factory remain application responsibilities. Do not keep user or request-specific mutable atoms in server module scope; create independent state for every SSR request and keep the server and client initial values aligned.

When state must survive a reload, see the Persist reference and compose @zhuangtai-js/persist into the state creator. Persistence does not change the component adapter choice; keep storage and hydration at the state-module boundary.

  • useAtomValue: read-only subscription to an Atom or computed.
  • useSetAtom: a setter that does not subscribe to the value.
  • useAtom: read-write access.
  • createAtomHook and createComputedHook: use these when you want argument-free bound hooks.

See the React reference for complete signatures and subscription semantics.