Skip to main content
Jotai provides powerful debugging tools that integrate with both React DevTools and Redux DevTools. This guide covers debugging strategies and tools.

Debug Labels

Debug labels help identify atoms in DevTools.

Manual Debug Labels

import { atom } from 'jotai'

const countAtom = atom(0)

if (process.env.NODE_ENV !== 'production') {
  countAtom.debugLabel = 'count'
}

Automatic Debug Labels

Use Babel or SWC plugins to automatically add debug labels:
1

Install plugin

npm install --save-dev jotai-babel
2

Configure Babel

// babel.config.js
module.exports = {
  plugins: ['jotai-babel/plugin-debug-label']
}
3

Or configure SWC

// .swcrc
{
  "jsc": {
    "experimental": {
      "plugins": [['@swc-jotai/debug-label', {}]]
    }
  }
}
The plugin automatically transforms:
// You write:
const countAtom = atom(0)

// Plugin adds:
const countAtom = atom(0)
countAtom.debugLabel = 'countAtom'

React DevTools

Inspect atoms using React DevTools.

useAtom Hook Inspection

Select a component using Jotai atoms in React DevTools. You’ll see “Atom” hooks with their current values:
function Counter() {
  const [count] = useAtom(countAtom) // Shows "Atom: count = 0"
  const [double] = useAtom(doubleAtom) // Shows "Atom: double = 0"
}

useAtomsDebugValue

See all atoms in the component tree:
import { useAtomsDebugValue } from 'jotai-devtools/utils'
import { Provider } from 'jotai'

function DebugAtoms() {
  useAtomsDebugValue()
  return null
}

function App() {
  return (
    <Provider>
      <DebugAtoms />
      <YourApp />
    </Provider>
  )
}
In React DevTools, navigate to the DebugAtoms component to see all atom values and their dependents.

Redux DevTools

Use Redux DevTools for advanced debugging features.

useAtomDevtools

Debug a specific atom:
import { useAtomDevtools } from 'jotai-devtools/utils'
import { useAtom, atom } from 'jotai'

const countAtom = atom(0)
countAtom.debugLabel = 'count'

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  
  // Attach to Redux DevTools
  useAtomDevtools(countAtom)
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  )
}

useAtomsDevtools

Debug all atoms in the store:
import { useAtomsDevtools } from 'jotai-devtools/utils'
import { Provider } from 'jotai'

function DebugAtoms() {
  useAtomsDevtools('demo')
  return null
}

function App() {
  return (
    <Provider>
      <DebugAtoms />
      <YourApp />
    </Provider>
  )
}
Redux DevTools will show:
{
  "values": {
    "atom1:count": 0,
    "atom2:doubleCount": 0,
    "atom3:half": 0
  },
  "dependents": {
    "atom1:count": ["atom2:doubleCount"],
    "atom2:doubleCount": ["atom3:half"],
    "atom3:half": []
  }
}

Redux DevTools Features

Time Travel

Jump to any previous state:
  1. Open Redux DevTools
  2. Click on any action in the history
  3. Hover over an action
  4. Click “Jump” to restore that state

Pause Recording

Pause state recording:
  1. Open Redux DevTools
  2. Click the pause button
  3. Changes won’t be recorded until you resume

Dispatch Actions

Manually set atom values:
  1. Open Redux DevTools
  2. Click “Show Dispatcher”
  3. Enter new value (will be parsed with JSON.parse)
  4. Click “Dispatch”
Example:
5
This sets the atom value to 5.
Values must be valid JSON. Complex values like functions cannot be dispatched.

Frozen Atoms

Prevent accidental mutations with freezeAtom:

freezeAtom

import { atom } from 'jotai'
import { freezeAtom } from 'jotai/utils'

const objAtom = freezeAtom(atom({ count: 0, name: 'test' }))

function Component() {
  const [obj, setObj] = useAtom(objAtom)
  
  // This will throw an error in development
  obj.count = 1 // Error: Cannot assign to read only property
  
  // Correct way: create new object
  setObj({ ...obj, count: 1 })
}

freezeAtomCreator

Create a factory for frozen atoms:
import { atom } from 'jotai'
import { freezeAtomCreator } from 'jotai/utils'

const freezeAtom = freezeAtomCreator(atom)

const userAtom = freezeAtom({ id: '1', name: 'John' })
const settingsAtom = freezeAtom({ theme: 'dark' })

// All atoms are frozen

Logging Atom Changes

Log atom changes for debugging:
import { atom, useAtom } from 'jotai'

const countAtom = atom(0)

const countAtomWithLog = atom(
  (get) => get(countAtom),
  (get, set, newValue) => {
    console.log('Count changed from', get(countAtom), 'to', newValue)
    set(countAtom, newValue)
  }
)

function Counter() {
  const [count, setCount] = useAtom(countAtomWithLog)
  // Every change is logged
}

Visual Debugger

Use the Jotai DevTools package for a visual debugger:
npm install jotai-devtools
import { DevTools } from 'jotai-devtools'
import 'jotai-devtools/styles.css'

function App() {
  return (
    <>
      <DevTools />
      <YourApp />
    </>
  )
}
This provides:
  • Visual atom graph
  • Atom value inspector
  • Time travel debugging
  • Atom dependency visualization

Debugging Async Atoms

Debug async atoms with loading and error states:
import { atom, useAtom } from 'jotai'
import { loadable } from 'jotai/utils'

const asyncAtom = atom(async () => {
  const res = await fetch('/api/data')
  return res.json()
})

const loadableAtom = loadable(asyncAtom)

function Component() {
  const [loadable] = useAtom(loadableAtom)
  
  console.log('State:', loadable.state) // 'loading' | 'hasData' | 'hasError'
  
  if (loadable.state === 'loading') {
    console.log('Loading...')
  }
  if (loadable.state === 'hasData') {
    console.log('Data:', loadable.data)
  }
  if (loadable.state === 'hasError') {
    console.error('Error:', loadable.error)
  }
}

Tips

Use debug labels in production too. They help debug production issues without impacting performance.
Combine React DevTools for quick inspection and Redux DevTools for advanced debugging like time travel.
Use freezeAtom during development to catch accidental mutations early.
useAtomsDevtools shows atom dependencies, helping you understand your atom graph and optimize re-renders.

Build docs developers (and LLMs) love