| name | jotai-v2 |
| description | Jotai v2 (^2.12.x) complete API reference for atomic React state management. Covers atom, useAtom, useAtomValue, useSetAtom, Provider, createStore, getDefaultStore, derived atoms (read-only, write-only, read-write), async atoms, atomWithStorage, atomWithDefault, selectAtom, splitAtom, focusAtom, atomFamily, loadable, unwrap, useHydrateAtoms, atomWithLazy, atomWithReset, RESET, useResetAtom, atomWithRefresh, TypeScript typing, performance patterns, and testing. Use this skill whenever code imports from 'jotai' or 'jotai/utils', or when working with Jotai atoms, stores, or providers in any React component.
|
Jotai v2 API Reference
Atomic approach to global React state management. Core ~2kb, TypeScript oriented, React 18 compatible. State is globally accessible, derived state via dependency tracking, automatic re-render elimination.
Source: https://jotai.org/docs (v2)
Extended references:
references/patterns-and-testing.md -- Common patterns, testing, SSR hydration
1. Core API
1.1 atom
import { atom } from 'jotai'
Creates an atom config (definition, not value). Configs are immutable; values live in stores.
Signatures:
function atom<Value>(initialValue: Value): PrimitiveAtom<Value>
function atom<Value>(read: (get: Getter) => Value): Atom<Value>
function atom<Value, Args extends unknown[], Result>(
read: (get: Getter) => Value,
write: (get: Getter, set: Setter, ...args: Args) => Result,
): WritableAtom<Value, Args, Result>
function atom<Value, Args extends unknown[], Result>(
read: Value,
write: (get: Getter, set: Setter, ...args: Args) => Result,
): WritableAtom<Value, Args, Result>
Primitive atoms:
const countAtom = atom(0)
const msgAtom = atom('hello')
const objAtom = atom({ id: 12, name: 'item' })
const arrAtom = atom<string[]>([])
const nullableAtom = atom<number | null>(0)
Derived atoms (three patterns):
const doubleAtom = atom((get) => get(countAtom) * 2)
const writeOnlyAtom = atom(null, (get, set, discount: number) => {
set(priceAtom, get(priceAtom) - discount)
})
const readWriteAtom = atom(
(get) => get(priceAtom) * 2,
(get, set, newPrice: number) => set(priceAtom, newPrice / 2),
)
Key behaviors:
get in read: reactive, tracks dependencies
get in write: reads value, NOT tracked
set in write: invokes target atom's write. Accepts updater: set(atom, (prev) => prev + 1)
In render -- use useMemo for stable reference:
const valueAtom = useMemo(() => atom({ value }), [value])
onMount property:
const anAtom = atom(1)
anAtom.onMount = (setAtom) => {
setAtom((c) => c + 1)
return () => { }
}
Async read with abort signal:
const userAtom = atom(async (get, { signal }) => {
const res = await fetch(`/api/users/${get(userIdAtom)}`, { signal })
return res.json()
})
1.2 useAtom
import { useAtom } from 'jotai'
function useAtom<Value, Update>(
atom: WritableAtom<Value, Update>,
options?: { store?: Store },
): [Value, SetAtom<Update>]
const [count, setCount] = useAtom(countAtom)
setCount(10)
setCount((prev) => prev + 1)
Never create atoms inline -- causes infinite loop:
1.3 useAtomValue
import { useAtomValue } from 'jotai'
const count = useAtomValue(countAtom)
1.4 useSetAtom
import { useSetAtom } from 'jotai'
const setCount = useSetAtom(countAtom)
1.5 Provider
import { Provider } from 'jotai'
Provides isolated state per subtree. Without Provider, uses default global store (provider-less mode). Use to: (1) isolate subtree state, (2) accept initial values, (3) clear all atoms via remount.
const myStore = createStore()
<Provider store={myStore}><App /></Provider>
1.6 createStore / getDefaultStore / useStore
import { createStore, getDefaultStore, useStore } from 'jotai'
const store = createStore()
store.get(countAtom)
store.set(countAtom, 1)
const unsub = store.sub(countAtom, () => { })
const defaultStore = getDefaultStore()
const store = useStore()
2. Utilities (jotai/utils)
2.1 atomWithStorage
import { atomWithStorage, createJSONStorage, RESET } from 'jotai/utils'
const darkModeAtom = atomWithStorage('darkMode', false)
Params: key (string), initialValue, storage? (custom impl), options? { getOnInit?: boolean }
Default: localStorage, JSON serialization, cross-tab sync. Set getOnInit: true to use stored value on init.
setText(RESET)
Custom storage:
const storage = createJSONStorage(() => sessionStorage, { reviver, replacer })
const myAtom = atomWithStorage('key', defaultValue, storage)
Validation with Zod:
import { unstable_withStorageValidator as withStorageValidator } from 'jotai/utils'
const isValid = (v: unknown) => schema.safeParse(v).success
const atom = atomWithStorage('key', 0, withStorageValidator(isValid)(createJSONStorage()))
2.2 atomWithDefault
import { atomWithDefault } from 'jotai/utils'
const count2 = atomWithDefault((get) => get(count1Atom) * 2)
2.3 atomWithReset / RESET / useResetAtom
import { atomWithReset, useResetAtom, RESET } from 'jotai/utils'
const dollarsAtom = atomWithReset(0)
2.4 atomWithRefresh
import { atomWithRefresh } from 'jotai/utils'
const postsAtom = atomWithRefresh((get) =>
fetch('/api/posts').then((r) => r.json()),
)
2.5 atomWithLazy
import { atomWithLazy } from 'jotai/utils'
const expensiveAtom = atomWithLazy(computeExpensiveValue)
2.6 selectAtom
import { selectAtom } from 'jotai/utils'
function selectAtom<Value, Slice>(
anAtom: Atom<Value>,
selector: (v: Value, prevSlice?: Slice) => Slice,
equalityFn?: (a: Slice, b: Slice) => boolean,
): Atom<Slice>
Read-only derived atom from a slice. Escape hatch -- prefer plain derived atoms.
const nameAtom = selectAtom(personAtom, (p) => p.name)
const birthAtom = selectAtom(personAtom, (p) => p.birth, deepEquals)
Selector MUST be stable (define outside component or use useCallback).
2.7 splitAtom
import { splitAtom } from 'jotai/utils'
type SplitAtom = <Item, Key>(
arrayAtom: PrimitiveAtom<Array<Item>>,
keyExtractor?: (item: Item) => Key,
): Atom<Array<PrimitiveAtom<Item>>>
Splits array atom into per-item atoms. Write provides dispatch: { type: 'remove', atom }, { type: 'insert', value, before? }, { type: 'move', atom, before }.
const todosAtom = atom([{ task: 'A', done: false }])
const todoAtomsAtom = splitAtom(todosAtom)
const [todoAtoms, dispatch] = useAtom(todoAtomsAtom)
dispatch({ type: 'remove', atom: todoAtoms[0] })
2.8 focusAtom (jotai-optics)
import { focusAtom } from 'jotai-optics'
const objectAtom = atom({ a: 5, b: 10 })
const aAtom = focusAtom(objectAtom, (optic) => optic.prop('a'))
2.9 atomFamily (deprecated, migrate to jotai-family)
const todoFamily = atomFamily((id: number) => atom({ id, text: '', done: false }))
todoFamily(1)
2.10 loadable
import { loadable } from 'jotai/utils'
const loadableAtom = loadable(asyncAtom)
2.11 unwrap
import { unwrap } from 'jotai/utils'
const syncAtom = unwrap(asyncAtom)
const syncAtom = unwrap(asyncAtom, (prev) => prev ?? 0)
2.12 useHydrateAtoms
import { useHydrateAtoms } from 'jotai/utils'
function useHydrateAtoms(
values: Iterable<readonly [Atom<unknown>, unknown]>,
options?: { store?: Store; dangerouslyForceHydrate?: boolean },
): void
useHydrateAtoms([[countAtom, 42], [nameAtom, 'Jotai']])
3. Async Atoms
Async read functions suspend by default. Wrap in <Suspense>. Use signal for abort on dependency change. Use loadable() or unwrap() to avoid Suspense.
<Suspense fallback="Loading...">
<UserName /> {}
</Suspense>
4. TypeScript
Requires TS 3.8+, strictNullChecks: true.
const numAtom = atom(0)
const derived = atom((get) => get(numAtom) * 2)
const writeOnly = atom<null, [string], void>(null, (_g, set, v) => set(strAtom, v))
const readWrite = atom<number, [number], void>(
(get) => get(numAtom),
(_g, set, v) => set(numAtom, v),
)
import { ExtractAtomValue, type PrimitiveAtom } from 'jotai'
type User = ExtractAtomValue<typeof userAtom>
5. Performance
- Granular atoms: split state into small atoms; each triggers re-render only in subscribers
- useSetAtom: write-only, no subscription = no re-render
- selectAtom: subscribe to slice of large object
- splitAtom: per-item atoms in lists avoid full list re-render
- Heavy computation outside render: do in write atoms, not derived read
- React 18: components are idempotent, extra renders normal; keep renders cheap
6. Import Map
| Import | Source |
|---|
atom, useAtom, useAtomValue, useSetAtom | 'jotai' |
Provider, useStore, createStore, getDefaultStore | 'jotai' |
atomWithStorage, createJSONStorage, RESET | 'jotai/utils' |
atomWithDefault, atomWithReset, useResetAtom | 'jotai/utils' |
atomWithRefresh, atomWithLazy | 'jotai/utils' |
selectAtom, splitAtom, loadable, unwrap | 'jotai/utils' |
useHydrateAtoms | 'jotai/utils' |
atomFamily | 'jotai/utils' (deprecated) / 'jotai-family' |
focusAtom | 'jotai-optics' (+ optics-ts) |
ExtractAtomValue, PrimitiveAtom | 'jotai' (types) |
7. Deprecations
| API | Status | Migration |
|---|
atomFamily in jotai/utils | Deprecated, removed in v3 | npm install jotai-family then import from 'jotai-family' |
selectAtom | Escape hatch | Prefer plain derived atoms |