Setting Up State Management (Jotai) for React Application
Jotai builds state from atoms — small isolated units that can be combined. Closest analogy is Recoil, but without boilerplate and with better TypeScript support out of the box. A component subscribes only to the atoms it reads and re-renders only when they change.
Good fit for applications with lots of independent pieces of state: forms, UI state, request cache.
What's Involved
Setting up atomic state structure for a project: basic atoms, derived atoms, async atoms, integration with React Suspense, devtools connection, file organization.
Installation
npm install jotai
# optionally — utilities
npm install jotai-devtools
Basic Atoms
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
// primitive atom
export const countAtom = atom(0)
// derived atom (readonly)
export const doubleCountAtom = atom((get) => get(countAtom) * 2)
// derived atom with write
export const incrementAtom = atom(
(get) => get(countAtom),
(get, set, step: number = 1) => set(countAtom, get(countAtom) + step)
)
function Counter() {
const [count, setCount] = useAtom(countAtom)
const double = useAtomValue(doubleCountAtom)
const increment = useSetAtom(incrementAtom)
return (
<div>
<span>{count} (×2 = {double})</span>
<button onClick={() => increment(1)}>+1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
)
}
useAtomValue — read-only subscription, doesn't return setter. useSetAtom — setter only, component doesn't re-render when atom changes.
Async Atoms and Suspense
import { atom } from 'jotai'
const userIdAtom = atom<number | null>(null)
// async derived atom
export const userAtom = atom(async (get) => {
const id = get(userIdAtom)
if (!id) return null
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error('User not found')
return res.json() as Promise<User>
})
// Component wrapped in Suspense
function UserProfile() {
const user = useAtomValue(userAtom) // suspend until loaded
if (!user) return <p>Select a user</p>
return <p>{user.name}</p>
}
function App() {
return (
<Suspense fallback={<Spinner />}>
<ErrorBoundary fallback={<Error />}>
<UserProfile />
</ErrorBoundary>
</Suspense>
)
}
Timeline
Basic Jotai setup — 1–2 hours. Integrating async atoms with Suspense — 2–3 hours. Complex state tree with derived atoms — 1 day.







