Loading...
Loading...
Expert guidance for Jotai state management in React applications. Provides best practices, performance optimization, and architectural patterns. Use when designing atom structures, implementing state management, optimizing re-renders, handling async state, integrating with TypeScript, or reviewing Jotai code for performance issues. Triggers on tasks involving Jotai atoms, derived state, focusAtom, splitAtom, atomFamily, or state management architecture decisions.
npx skill4agent add s-hiraoku/skills-factory jotai-expertNeed state management?
├── Simple local state → useState (no Jotai needed)
├── Shared state across components
│ ├── Few components, same tree → Context may suffice
│ └── Many components, complex → Use Jotai ✓
└── Global app state → Use Jotai ✓
Choosing atom type:
├── Static value → atom(initialValue)
├── Computed from other atoms → atom((get) => ...)
├── Need to modify other atoms → atom(null, (get, set) => ...)
├── Persist to storage → atomWithStorage()
├── List of items → splitAtom() or atoms-in-atom
└── Parameterized data → atomFamily()
Performance issue?
├── Component re-renders too often
│ ├── Only reading? → useAtomValue
│ ├── Only writing? → useSetAtom
│ ├── Large object? → selectAtom / focusAtom
│ └── List items? → splitAtom
└── Async loading states → loadable() or manual loading atomsimport { atom } from 'jotai'
// 1. Primitive: holds value
const countAtom = atom(0)
// 2. Derived read-only: computed from others
const doubleAtom = atom((get) => get(countAtom) * 2)
// 3. Derived read-write: custom setter
const celsiusAtom = atom(0)
const fahrenheitAtom = atom(
(get) => get(celsiusAtom) * 9/5 + 32,
(get, set, newF: number) => set(celsiusAtom, (newF - 32) * 5/9)
)
// 4. Write-only (action): no read value
const incrementAtom = atom(null, (get, set) => {
set(countAtom, get(countAtom) + 1)
})| Need | Hook | Re-renders on change |
|---|---|---|
| Read only | | Yes |
| Write only | | No |
| Both | | Yes |
// ❌ WRONG: Creates new atom every render
function Component() {
const myAtom = atom(0) // Unstable reference
}
// ✅ CORRECT: Define outside component
const myAtom = atom(0)
function Component() {
const [value] = useAtom(myAtom)
}
// ✅ CORRECT: useMemo for dynamic atoms
function Component({ id }) {
const itemAtom = useMemo(() => atom(items[id]), [id])
}// ❌ BAD: Re-renders on any user field change
function UserProfile() {
const [user] = useAtom(userAtom)
return <h1>{user.name}</h1>
}
// ✅ GOOD: Only re-renders when name changes
import { selectAtom } from 'jotai/utils'
const nameAtom = selectAtom(userAtom, (u) => u.name)
function UserName() {
const name = useAtomValue(nameAtom)
return <h1>{name}</h1>
}import { splitAtom } from 'jotai/utils'
const todosAtom = atom<Todo[]>([])
const todoAtomsAtom = splitAtom(todosAtom, (t) => t.id)
function TodoList() {
const [todoAtoms] = useAtom(todoAtomsAtom)
return todoAtoms.map((todoAtom) => (
<TodoItem key={todoAtom.toString()} atom={todoAtom} />
))
}
// Each item updates independently
function TodoItem({ atom }) {
const [todo, setTodo] = useAtom(atom)
// Changes here don't re-render other items
}import { focusAtom } from 'jotai-optics'
const formAtom = atom({ name: '', email: '', address: { city: '' } })
// Focused atoms for each field
const nameAtom = focusAtom(formAtom, (o) => o.prop('name'))
const cityAtom = focusAtom(formAtom, (o) => o.prop('address').prop('city'))
// Each field component only re-renders when its value changesimport { loadable } from 'jotai/utils'
const asyncDataAtom = atom(async () => fetch('/api').then(r => r.json()))
const loadableDataAtom = loadable(asyncDataAtom)
function Component() {
const data = useAtomValue(loadableDataAtom)
if (data.state === 'loading') return <Spinner />
if (data.state === 'hasError') return <Error />
return <Data value={data.data} />
}