Recipes
Common flows across the React adapter’s features.
Toggle + content separation
const sidebar = openAtom("sidebar");
function Toggle() { return <button onClick={() => sidebar.toggle()}>≡</button>;}
function Sidebar() { const opened = sidebar.useOpened(); return opened ? <Drawer /> : null;}Modal with payload
const editUserModal = portalAtom<{ id: number; initialName: string }>("editUser");
// From a list:<button onClick={() => editUserModal.open({ id: u.id, initialName: u.name })}>Edit</button>
// In the modal component:function EditUserModal() { const opened = editUserModal.useOpened(); const data = editUserModal.useData(); if (!opened) return null; return <Dialog onClose={() => editUserModal.close()}>...{data.initialName}...</Dialog>;}Fetch flow with fetchingAtom
const usersAtom = fetchingAtom<User[]>("users");
export async function loadUsers() { usersAtom.startLoading(); try { const data = await api.users.list(); usersAtom.success(data); } catch (err) { usersAtom.failed(err); }}
function UsersList() { const isLoading = usersAtom.useLoading(); const data = usersAtom.useData(); const error = usersAtom.useError(); if (isLoading) return <Spinner />; if (error) return <p className="text-red-500">{String(error)}</p>; return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;}For richer caching/invalidation, use @mongez/atomic-query instead.
Granular subscriptions on object atoms
const profile = atom({ key: "profile", default: { name: "Anon", email: "", lastSeen: 0 },});
// Only re-renders when `name` changes.function NameDisplay() { const name = profile.use("name"); return <span>{name}</span>;}
// Updating `lastSeen` does NOT re-render `NameDisplay`.setInterval(() => profile.change("lastSeen", Date.now()), 1000);SSR snapshot/hydrate (Next.js)
// app/layout.tsx (server component)import { createAtomStore } from "@mongez/atom";import { AtomStoreProvider, HydrateAtomsScript } from "@mongez/react-atom";import { userAtom } from "@/state/user";
export default function Layout({ children, currentUser }) { const store = createAtomStore(); store.use(userAtom).update(currentUser); return ( <AtomStoreProvider store={store}> {children} <HydrateAtomsScript snapshot={store.snapshot()} /> </AtomStoreProvider> );}"use client";import { AtomStoreProvider, readHydration } from "@mongez/react-atom";
export function ClientRoot({ children }) { return ( <AtomStoreProvider initialValues={readHydration() ?? undefined}> {children} </AtomStoreProvider> );}Action method from an event handler (SSR-safe)
import { useAtom } from "@mongez/react-atom";
function LogoutButton() { const auth = useAtom(authAtom); // store-scoped under a provider return <button onClick={() => auth.logout()}>Log out</button>;}Without useAtom(authAtom), calling authAtom.logout() would mutate the module-level template — fine on the client, wrong on the server.
Atom for an external library’s state
<atom.Provider> is useful when an external library hands you state that you want to mirror into an atom.
const themeAtom = atom({ key: "theme", default: "light" as "light" | "dark" });
function App({ themeFromOS }) { return ( <themeAtom.Provider value={themeFromOS}> <RestOfApp /> </themeAtom.Provider> );}Devtools in a React dev build
// app entryimport { enableAtomDevtools } from "@mongez/atom";
if (process.env.NODE_ENV !== "production") { enableAtomDevtools({ name: "MyReactApp" });}Connects to Redux DevTools — every atom (including fetchingAtoms and portalAtoms) appears in the timeline.