Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to use Jotai in React

Jotai is an atomic state management library for React that stores state as small, composable atoms — individual pieces of state that components subscribe to directly, re-rendering only when that specific atom changes. As the creator of CoreUI with 25 years of front-end development experience, I use Jotai when an application needs fine-grained reactive state updates with minimal overhead and no external state management infrastructure. Jotai atoms are simpler than Redux slices and more granular than Zustand stores, making them ideal for UI state like filter values, modal open/close, or selected items in a list. The API is intentionally minimal — just atom() and useAtom().

Create atoms and use them in components.

// atoms/filterAtoms.js
import { atom } from 'jotai'

// Primitive atoms
export const searchAtom = atom('')
export const categoryAtom = atom('all')
export const pageAtom = atom(1)

// Derived atom - computed from other atoms
export const activeFiltersAtom = atom((get) => {
  const search = get(searchAtom)
  const category = get(categoryAtom)
  return {
    hasFilters: search !== '' || category !== 'all',
    count: (search ? 1 : 0) + (category !== 'all' ? 1 : 0)
  }
})
// ProductList.jsx
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { searchAtom, categoryAtom, activeFiltersAtom } from './atoms/filterAtoms'

export function SearchBar() {
  const [search, setSearch] = useAtom(searchAtom)

  return (
    <input
      value={search}
      onChange={e => setSearch(e.target.value)}
      placeholder="Search..."
    />
  )
}

export function CategoryFilter() {
  const [category, setCategory] = useAtom(categoryAtom)

  return (
    <select value={category} onChange={e => setCategory(e.target.value)}>
      <option value="all">All</option>
      <option value="electronics">Electronics</option>
      <option value="clothing">Clothing</option>
    </select>
  )
}

export function FilterBadge() {
  // useAtomValue - read only, no setter
  const { hasFilters, count } = useAtomValue(activeFiltersAtom)

  if (!hasFilters) return null
  return <span className="badge">{count} filters active</span>
}

export function ResetButton() {
  // useSetAtom - write only, no subscription, no re-renders
  const setSearch = useSetAtom(searchAtom)
  const setCategory = useSetAtom(categoryAtom)
  const setPage = useSetAtom(pageAtom)

  function reset() {
    setSearch('')
    setCategory('all')
    setPage(1)
  }

  return <button onClick={reset}>Reset Filters</button>
}

useAtomValue subscribes read-only — the component re-renders when the atom changes but gets no setter. useSetAtom writes without subscribing — ResetButton never re-renders when filter values change, only when its own parent re-renders.

Async Atoms

Load data with async atoms that integrate with React Suspense.

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

export const userIdAtom = atom(1)

// Async atom that fetches based on another atom
export const userAtom = atom(async (get) => {
  const id = get(userIdAtom)
  const res = await fetch(`/api/users/${id}`)
  return res.json()
})

// loadable wrapper to avoid Suspense boundary
export const userLoadableAtom = loadable(userAtom)
// UserProfile.jsx
import { useAtomValue, useSetAtom } from 'jotai'
import { userIdAtom, userLoadableAtom } from './atoms/userAtoms'

function UserProfile() {
  const userLoadable = useAtomValue(userLoadableAtom)
  const setUserId = useSetAtom(userIdAtom)

  if (userLoadable.state === 'loading') return <p>Loading...</p>
  if (userLoadable.state === 'hasError') return <p>Error</p>

  const user = userLoadable.data
  return <div>{user.name}</div>
}

loadable converts an async atom into a synchronous atom that returns { state: 'loading' | 'hasData' | 'hasError', data }, avoiding the need for Suspense boundaries.

Best Practice Note

This is the same atomic state approach we recommend in CoreUI React templates for UI state. Jotai atoms are stored in a global WeakMap — no Provider is needed in most cases. For complex application state with many interdependent slices, Zustand or Redux Toolkit may be more appropriate. See how to use Zustand in React for comparison with a store-based approach.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team