Global state doesn't have to be global. Jotai gives you atoms — tiny pieces of state that compose together. Only components reading an atom re-render when it changes.
What Is Jotai?
Jotai takes an atomic approach to React state management. Instead of one big store, you create independent atoms that components subscribe to individually. Think of it as "React useState, but shareable."
Quick Start
npm install jotai
import { atom, useAtom } from 'jotai';
// Create atoms
const countAtom = atom(0);
const nameAtom = atom('World');
// Derived atom — automatically updates
const greetingAtom = atom((get) => `Hello, ${get(nameAtom)}! Count: ${get(countAtom)}`);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
function Name() {
const [name, setName] = useAtom(nameAtom);
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}
function Greeting() {
const [greeting] = useAtom(greetingAtom);
return <p>{greeting}</p>; // Only re-renders when count OR name changes
}
Atoms Are Composable
// Primitive atom
const usersAtom = atom([]);
const filterAtom = atom('');
// Derived atom — auto-computed
const filteredUsersAtom = atom((get) => {
const users = get(usersAtom);
const filter = get(filterAtom).toLowerCase();
return users.filter(u => u.name.toLowerCase().includes(filter));
});
// Async atom — fetches data
const userDataAtom = atom(async () => {
const res = await fetch('/api/users');
return res.json();
});
// Write-only atom — actions
const addUserAtom = atom(null, (get, set, newUser) => {
set(usersAtom, [...get(usersAtom), newUser]);
});
Jotai vs. Zustand vs. Redux
| Feature | Jotai | Zustand | Redux |
|---|---|---|---|
| Mental model | Atoms (bottom-up) | Store (top-down) | Store (top-down) |
| Re-renders | Per-atom | Per-selector | Per-selector |
| Boilerplate | Minimal | Minimal | Heavy |
| Best for | Fine-grained state | Simple global state | Large teams |
| Async | Built-in atoms | Manual async | Thunks/Sagas |
| DevTools | Yes | Yes | Yes |
| React Suspense | Native | No | No |
Integrations
Persistence
import { atomWithStorage } from 'jotai/utils';
const themeAtom = atomWithStorage('theme', 'light');
// Automatically syncs with localStorage
React Query Integration
import { atomWithQuery } from 'jotai-tanstack-query';
const userAtom = atomWithQuery(() => ({
queryKey: ['user'],
queryFn: async () => {
const res = await fetch('/api/user');
return res.json();
},
}));
Immer for Immutable Updates
import { atomWithImmer } from 'jotai-immer';
const todosAtom = atomWithImmer([]);
// Mutate directly — Immer handles immutability
const addTodoAtom = atom(null, (get, set, title) => {
set(todosAtom, (draft) => {
draft.push({ id: Date.now(), title, done: false });
});
});
Key Features
- Atomic model — fine-grained re-renders
- Suspense support — async atoms work with React Suspense
- No providers needed (optional Provider for isolation)
- TypeScript-first — full type inference
- Tiny — 3KB bundle
- Rich ecosystem — query, immer, storage, URQL, xstate
Get Started
- Documentation
- GitHub — 19K+ stars
- Tutorial
Need fresh data atoms for your app? My Apify scrapers deliver structured web data. Custom solutions: spinov001@gmail.com
Top comments (0)