@mongez/react-atom
Version:
A simple state management tool for React Js.
167 lines (119 loc) • 7.44 kB
Plain Text
@mongez/react-atom — full reference
React adapter for `@mongez/atom`. Every atom carries `useState` / `useValue` / `use(key)` / `useWatch` / `Provider`. `<AtomStoreProvider>` scopes state per SSR request.
# Install
```sh
yarn add @mongez/react-atom
peer: react >= 18, @mongez/atom
```
# Public exports
```ts
import {
// Atom factories
atom,
atomCollection,
// Preset atoms
openAtom,
loadingAtom,
fetchingAtom,
portalAtom,
// SSR / store
AtomStoreProvider,
AtomStoreContext,
useAtom,
useAtomStore,
// Legacy shims (deprecated, kept for migration)
AtomContext, // alias of AtomStoreContext
AtomProvider, // shim over AtomStoreProvider
// Hydration helpers
HydrateAtomsScript,
readHydration,
serializeSnapshot,
serializeStore,
DEFAULT_HYDRATION_SCRIPT_ID,
// Types
type ReactAtom,
type ReactActions,
type OpenAtomActions,
type OpenAtomType,
type LoadingAtom,
type LoadingAtomActions,
type FetchingAtomType,
type FetchingAtomActions,
type AtomPortal,
type PortalActions,
type AtomStoreProviderProps,
type HydrateAtomsScriptProps,
} from "@mongez/react-atom";
```
# atom(options)
**Auto-trigger:** code imports `atom`, `atomCollection`, or `useAtom` from `@mongez/react-atom`; code calls `.useValue()`, `.useState()`, `.use(key)`, `.useWatch(key, cb)`, or renders `<someAtom.Provider>`; user asks "how do I create an atom in React", "what's the difference between useValue and useState", "how do I subscribe to one key only", or "how do I add a custom action to a React atom"; typical import `import { atom, atomCollection } from "@mongez/react-atom"`.
**Skip when:** framework-agnostic atom primitive (`createAtom`, `createAtomCollection`) — that lives in `@mongez/atom`, the core layer this package sits on top of; preset-atom shorthands (`openAtom`/`loadingAtom`/`fetchingAtom`/`portalAtom`) use `mongez-react-atom-presets`; per-request SSR scoping and hydration use `mongez-react-atom-ssr`; copy-paste end-to-end flows use `mongez-react-atom-recipes`.
Same shape as `createAtom` from `@mongez/atom`. Returns a `ReactAtom<V, A>` — a `Atom<V, A>` plus the React-aware actions injected by this package.
## Hooks every atom carries
- `atom.useValue(): V` — subscribe, return whole value.
- `atom.useState(): [V, (next | (prev) => next) => void]` — like React's useState.
- `atom.use<K extends keyof V>(key: K): V[K]` — subscribe to one key only.
- `atom.useWatch<K>(key, cb)` — effect-style watcher.
- `<atom.Provider value={partial}>` — pushes value into the atom on mount.
All hooks use `useSyncExternalStore`; reads are tear-free under React 18 concurrent rendering. All hooks honor the nearest `<AtomStoreProvider>` — they operate on the store-scoped clone when one is mounted, fall back to the template otherwise.
# atomCollection(options)
React-aware version of `atomCollection` from `@mongez/atom`. Inherits the array helpers (`push`, `unshift`, `pop`, ...) AND the React hooks.
# Preset atoms
**Auto-trigger:** code imports or calls `openAtom`, `loadingAtom`, `fetchingAtom`, or `portalAtom` from `@mongez/react-atom`; code uses `useOpened`, `useLoading`, `useData`, `useError`, `usePagination`, `startLoading`, `stopLoading`, `toggleLoading`, `success`, `failed`, `append`, `prepend`, or `toggle` methods on a preset atom; user asks "how do I make a toggle / open-close atom", "how do I model a loading flag", "how do I do a fetch lifecycle with isLoading/data/error", or "how do I coordinate a modal/drawer"; typical import `import { openAtom, portalAtom } from "@mongez/react-atom"`.
**Skip when:** hand-rolling a custom action atom from scratch — use `mongez-react-atom-atoms`; richer cache-keyed server state (invalidation, refetch on focus) belongs in `@mongez/atomic-query`, not `fetchingAtom`; SSR scoping/hydration of preset atoms still uses `mongez-react-atom-ssr`; end-to-end flow examples use `mongez-react-atom-recipes`.
## openAtom(key, defaultOpened = false)
```ts
const m = openAtom("sidebar");
m.open(); m.close(); m.toggle();
const opened = m.useOpened();
```
## loadingAtom(key, defaultLoading = false)
```ts
const l = loadingAtom("fetching.users");
l.startLoading(); l.stopLoading(); l.toggleLoading();
```
## fetchingAtom<DataType, PaginationType>(key, defaultValue?, defaultFetching = true)
Value shape: `{ isLoading, data, error, pagination? }`. Actions: `startLoading`, `stopLoading`, `success(data, pagination?)`, `failed(error)`, `append(data)`, `prepend(data)`. Hooks: `useLoading`, `useData`, `useError`, `usePagination`.
## portalAtom<T>(name, opened = false)
Value: `{ opened: boolean, data: T }`. Actions: `open(data?)`, `close()`, `toggle(data?)`, `useOpened()`, `useData()`. Key is suffixed with `-portal`.
# SSR
**Auto-trigger:** code imports `AtomStoreProvider`, `AtomStoreContext`, `useAtom`, `useAtomStore`, `HydrateAtomsScript`, `readHydration`, `serializeStore`, `serializeSnapshot`, `DEFAULT_HYDRATION_SCRIPT_ID`, `AtomStoreProviderProps`, or `HydrateAtomsScriptProps` from `@mongez/react-atom`; user asks "how do I avoid hydration mismatches with atoms in Next.js App Router", "how do I isolate atom state per SSR request", "how do I pre-fill atoms on the server", or "how do I call an action method safely under SSR"; typical import `import { AtomStoreProvider, HydrateAtomsScript, readHydration } from "@mongez/react-atom"`.
**Skip when:** client-only single-page apps where module-level singletons are fine — no provider needed, so reach for `mongez-react-atom-atoms` instead; preset atoms in a non-SSR context use `mongez-react-atom-presets`; the underlying store primitive `createAtomStore` and `snapshot()` itself live in `@mongez/atom`, the core layer this package wraps; mixed end-to-end flows use `mongez-react-atom-recipes`.
## `<AtomStoreProvider>`
```ts
type AtomStoreProviderProps = {
store?: AtomStore; // caller-owned if provided
initialAtoms?: Atom<any>[]; // pre-register
initialValues?: Record<string, unknown>; // silent-update on entry
children: React.ReactNode;
};
```
- Auto-creates a store if none is passed; destroys it on unmount.
- A passed-in store is not destroyed automatically (caller owns lifecycle).
## `useAtom`
```ts
useAtom<V, A>(template: Atom<V, A>): Atom<V, A>;
useAtom<V>(key: string): Atom<V> | undefined;
```
Template overload → store-scoped clone (or template itself when no provider). String overload → look up by key in the active store.
## `useAtomStore()`
Returns the active `AtomStore` or `null`.
# Hydration helpers
```ts
serializeSnapshot(snapshot, options?): string
serializeStore(store, options?): string
readHydration(id?: string = DEFAULT_HYDRATION_SCRIPT_ID): Record<string, unknown> | null
DEFAULT_HYDRATION_SCRIPT_ID = "__mongez_atom_state"
<HydrateAtomsScript
snapshot={Record | string}
id?={string}
nonce?={string}
```
Serializer protections: `</script>` is escaped to `<\/script>`; U+2028/U+2029 are escaped to `
`/`
`. The component uses `dangerouslySetInnerHTML` — safe because the serializer is the only path.
# React version
React 18+ only. `useSyncExternalStore` is required.
# What this package does NOT do
- The atom primitive itself → `@mongez/atom`
- Server-state caching (query keys, invalidation) → `@mongez/atomic-query`
- The event bus → `@mongez/events`