How to use `AtomStoreProvider`, `useAtom`, `useAtomStore`, and the hydration utilities (`HydrateAtomsScript`, `readHydration`, `serializeStore`, `serializeSnapshot`) for SSR-safe, per-request atom isolation and client hydration. TRIGGER when: code imports `AtomStoreProvider`, `AtomStoreContext`, `useAtom`, `useAtomStore`, `HydrateAtomsScript`, `readHydration`, `serializeStore`, `serializeSnapshot`, `DEFAULT_HYDRATION_SCRIPT_ID`, `AtomStoreProviderProps`, or `HydrateAtomsScriptProps` from `@mo...
Scanned 5/28/2026
Install via CLI
openskills install hassanzohdy/mongez-react-atom---
name: mongez-react-atom-ssr
description: |
How to use `AtomStoreProvider`, `useAtom`, `useAtomStore`, and the hydration utilities (`HydrateAtomsScript`, `readHydration`, `serializeStore`, `serializeSnapshot`) for SSR-safe, per-request atom isolation and client hydration.
TRIGGER when: 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: 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`.
---
# SSR & stores
The module-level atom registry is shared per Node process. To keep concurrent SSR requests from leaking state into each other, wrap your tree in `<AtomStoreProvider>` — each request gets its own scoped clones of every atom that's read inside it.
## `<AtomStoreProvider>`
```tsx
import { AtomStoreProvider } from "@mongez/react-atom";
<AtomStoreProvider>
<App />
</AtomStoreProvider>
```
Props:
```ts
type AtomStoreProviderProps = {
store?: AtomStore; // bring your own; otherwise auto-created
initialAtoms?: Atom<any>[]; // pre-register these
initialValues?: Record<string, unknown>; // hydration values keyed by atom key
children: React.ReactNode;
};
```
- An auto-created store is destroyed on unmount.
- A `store` passed via props is caller-owned (NOT destroyed by the provider).
- `initialValues` are applied via `silentUpdate` so the first render of subscribers sees the hydrated values; no flash.
## `useAtom(template | key)`
Two overloads.
```ts
useAtom(template: Atom<V, A>): Atom<V, A> // → store-scoped clone (or the template if no provider)
useAtom(key: string): Atom<V> | undefined // → scoped clone by key, or undefined
```
The per-atom hooks (`useValue`, `useState`, `use`, `useWatch`) already resolve internally — you only need `useAtom(template)` explicitly when you want to call an action method from an event handler in an SSR-safe way:
```tsx
function LogoutButton() {
const scopedAuth = useAtom(authAtom);
return <button onClick={() => scopedAuth.logout()}>Log out</button>;
}
```
The string overload is a legacy escape hatch — prefer the template overload.
## `useAtomStore()`
Returns the active `AtomStore` or `null` if no provider is mounted. Use for advanced cases: snapshotting on demand, manually hydrating, integrating with a non-React layer.
## Snapshot / Hydrate
```ts
serializeSnapshot(snapshot: Record<string, unknown>, options?: { replacer, space }): string
serializeStore(store: AtomStore, options?): string
readHydration(id?: string): Record<string, unknown> | null
<HydrateAtomsScript
snapshot={snapshotOrString}
id="__mongez_atom_state" // optional; default constant
nonce={cspNonce} // optional
/>
```
- `serializeSnapshot` / `serializeStore` produce a string safe to embed in `<script type="application/json">`: `</script>` and U+2028/U+2029 are escaped.
- `<HydrateAtomsScript>` renders an inline JSON script tag. Place it once per `<AtomStoreProvider>` you want to hydrate.
- `readHydration()` is the client-side companion: parses the JSON out of the script tag. Returns `null` if missing or malformed.
### Why hydrate at all?
Without hydration, atoms on the client start from their `default` value. For data the server already has (current user, feature flags, initial page data), this means the first render shows a loading state — or worse, a different UI than the server rendered — causing a **hydration mismatch** or a visible content flash.
Hydration lets you:
1. Fetch data once on the server (no extra client round-trip on load).
2. Inline the result as JSON in the HTML.
3. Have the client's atom start pre-filled — first render matches server HTML, no flash.
### Real-world example — pre-loading the current user (Next.js App Router)
```ts
// atoms/user.ts — shared between server and client
import { atom } from "@mongez/react-atom";
type User = { id: number; name: string; role: "admin" | "viewer" } | null;
export const currentUserAtom = atom<User>({ key: "currentUser", default: null });
```
```tsx
// app/layout.tsx — SERVER component
import { createAtomStore } from "@mongez/atom";
import { AtomStoreProvider, HydrateAtomsScript } from "@mongez/react-atom";
import { getCurrentUser } from "@/lib/auth"; // your server-side auth helper
import { currentUserAtom } from "@/atoms/user";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
// Runs on the server — no client round-trip needed
const user = await getCurrentUser();
const store = createAtomStore();
store.use(currentUserAtom).update(user); // pre-fill the store
return (
<html lang="en">
<body>
<AtomStoreProvider store={store}>
{children}
{/* Serialises atom state into an inline <script type="application/json"> tag */}
<HydrateAtomsScript snapshot={store.snapshot()} />
</AtomStoreProvider>
</body>
</html>
);
}
```
```tsx
// app/providers.tsx — CLIENT boundary
"use client";
import { AtomStoreProvider, readHydration } from "@mongez/react-atom";
export function ClientProviders({ children }: { children: React.ReactNode }) {
// readHydration() parses the JSON script tag injected by the server.
// Pass it as initialValues so the client store starts pre-filled.
return (
<AtomStoreProvider initialValues={readHydration() ?? undefined}>
{children}
</AtomStoreProvider>
);
}
```
```tsx
// components/UserMenu.tsx — any client component
"use client";
import { currentUserAtom } from "@/atoms/user";
export function UserMenu() {
const user = currentUserAtom.useValue();
// user is never null on first render — the server already fetched it.
// No loading spinner, no hydration mismatch.
if (!user) return <a href="/login">Sign in</a>;
return (
<div>
<span>{user.name}</span>
{user.role === "admin" && <a href="/admin">Admin</a>}
</div>
);
}
```
Without this pattern `currentUserAtom.useValue()` would be `null` on the client's first render even though the server knew the user — you'd see a brief "Sign in" flicker before the client fetched and re-rendered.
## End-to-end minimal example
```tsx
// server component
import { createAtomStore } from "@mongez/atom";
import { AtomStoreProvider, HydrateAtomsScript } from "@mongez/react-atom";
export default function Layout({ children }) {
const store = createAtomStore();
store.use(someAtom).update(serverFetchedValue);
return (
<AtomStoreProvider store={store}>
{children}
<HydrateAtomsScript snapshot={store.snapshot()} />
</AtomStoreProvider>
);
}
```
```tsx
// client root
"use client";
import { AtomStoreProvider, readHydration } from "@mongez/react-atom";
export function ClientRoot({ children }) {
return (
<AtomStoreProvider initialValues={readHydration() ?? undefined}>
{children}
</AtomStoreProvider>
);
}
```
## Concurrent isolation
`<AtomStoreProvider>` instances are independent. Two providers in different parts of the tree don't share state — useful for multi-tenant dashboards or A/B split views.
```tsx
<>
<AtomStoreProvider>
<TenantA />
</AtomStoreProvider>
<AtomStoreProvider>
<TenantB />
</AtomStoreProvider>
</>
```
No comments yet. Be the first to comment!