Jotai Atomic State

Core Concepts

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

// Primitive atom
const countAtom = atom(0)

// Derived (read-only) atom
const doubleCountAtom = atom((get) => get(countAtom) * 2)

// Read-write derived atom
const incrementAtom = atom(
  (get) => get(countAtom),
  (get, set, delta = 1) => set(countAtom, get(countAtom) + delta)
)

// Usage
function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const double = useAtomValue(doubleCountAtom)
  const increment = useSetAtom(incrementAtom)

  return (
    <div>
      <p>Count: {count}, Double: {double}</p>
      <button onClick={() => increment()}>+1</button>
    </div>
  )
}

Async Atoms

import { atom } from 'jotai'
import { atomWithQuery } from 'jotai-tanstack-query'

// Async atom (Suspense-based)
const userAtom = atom(async (get) => {
  const id = get(userIdAtom)
  const res = await fetch(`/api/users/${id}`)
  return res.json()
})

// With TanStack Query integration
const userQueryAtom = atomWithQuery((get) => ({
  queryKey: ['user', get(userIdAtom)],
  queryFn: () => fetchUser(get(userIdAtom)),
}))

Persistence with atomWithStorage

import { atomWithStorage } from 'jotai/utils'

// Persists to localStorage automatically
const themeAtom = atomWithStorage('theme', 'dark')
const tokenAtom = atomWithStorage('auth-token', null)

// Usage is identical to regular atom
function ThemeToggle() {
  const [theme, setTheme] = useAtom(themeAtom)
  return <button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
    {theme}
  </button>
}

When to Choose Jotai