Introduction
Jotai (Japanese for "state") takes inspiration from Recoil but simplifies the API to the bare minimum. Every piece of state is an atom, and components subscribe by calling useAtom. Derived atoms, async atoms, and persistence fall out naturally.
With over 21,000 GitHub stars, Jotai is a favorite in the Poimandres collective alongside Zustand and React Three Fiber. It scales from a single counter to complex apps without changing paradigm.
What Jotai Does
Jotai stores atom values in a React context provider. useAtom(atom) subscribes just that component, so updates are surgical — no selectors, no memoization. Atoms can depend on other atoms (derivation), be async (suspend while loading), or be persisted (via storage utilities).
Architecture Overview
atom(initial)
|
[Atom Store]
(per Provider, or default)
|
useAtom(a) -> [value, setter]
|
Derived atom: get(other) auto re-evaluates on deps change
Async atom: async get() suspends until resolved
Writable atom: (get, set, arg) => { ... }
|
Subscribed components re-render only when their atom changesSelf-Hosting & Configuration
import { atom, useAtom, Provider } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { atomFamily } from "jotai/utils";
const tokenAtom = atomWithStorage("auth-token", "");
const userQueryAtom = atom(async (get) => {
const token = get(tokenAtom);
const res = await fetch("/me", { headers: { Authorization: `Bearer ${token}` } });
return res.json();
});
const todoByIdAtom = atomFamily((id: string) =>
atom(async () => (await fetch(`/todos/${id}`)).json())
);
function Root() {
return (
<Provider> {/* optional; default store used if omitted */}
<Suspense fallback={<div>Loading...</div>}>
<App />
</Suspense>
</Provider>
);
}Key Features
- Atomic — fine-grained subscriptions, no selectors needed
- Derived atoms — compute from other atoms, auto-reactive
- Async atoms — work with Suspense or loadable wrappers
- atomWithStorage — persist to localStorage/sessionStorage/IndexedDB
- atomFamily — dynamic atoms keyed by arbitrary values
- Multiple stores — per-Provider stores for isolation (testing, SSR)
- Integrations — jotai-tanstack-query, jotai-redux, jotai-xstate
- Tiny — ~3KB core, no required context boilerplate
Comparison with Similar Tools
| Feature | Jotai | Zustand | Redux Toolkit | Recoil | Valtio |
|---|---|---|---|---|---|
| Model | Atoms | Store | Single store | Atoms | Proxy |
| Size | ~3KB | ~3KB | ~11KB | ~20KB | ~3KB |
| Async | Native | Manual | RTK Query | Native | Manual |
| DevTools | Via plugin | Yes | Yes (redux-devtools) | Yes | Via plugin |
| Suspense | Yes | No | No | Yes | No |
| Best For | Granular state | Simple global state | Large apps / Redux shops | Facebook-style atoms (sunset) | Mutable feel |
FAQ
Q: Jotai vs Zustand — both are Poimandres? A: Zustand is a single store (like Redux but tiny). Jotai is many atoms. Use Zustand for globally shared state with selectors; use Jotai when state is fragmented across features.
Q: Is Jotai a Recoil replacement? A: Yes, and now that Recoil is unmaintained, Jotai is the recommended atomic-state library. Migration is mostly renaming + adjusting async semantics.
Q: Does Jotai work with SSR/Next.js?
A: Yes. Use createStore() per request to avoid cross-request leaks. The Next.js + Jotai guide in the docs covers App Router specifics.
Q: How do I debug atom updates?
A: Install jotai-devtools for a Redux-DevTools-style UI, or use useAtomsDebugValue() for React DevTools integration.
Sources
- GitHub: https://github.com/pmndrs/jotai
- Docs: https://jotai.org
- Org: Poimandres
- License: MIT