Primitive and flexible state management for React



import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
const darkModeAtom = atomWithStorage('darkMode', false)
const Page = () => {
const [darkMode, setDarkMode] = useAtom(darkModeAtom)
return (
<h1>Welcome to {darkMode ? 'dark' : 'light'} mode!</h1>
<button onClick={() => setDarkMode(!darkMode)}>toggle theme</button>

The atomWithStorage function creates an atom with a value persisted in localStorage or sessionStorage for React or AsyncStorage for React Native.


key (required): a unique string used as the key when syncing state with localStorage, sessionStorage, or AsyncStorage

initialValue (required): the initial value of the atom

storage (optional): an object with:

  • getItem and setItem methods for storing/retreiving persisted state; defaults to using localStorage for storage/retreival and JSON.stringify()/JSON.parse() for serialization/deserialization.
  • Optionally, the storage has a subscribe property, which can be used to synchronize storage. The default localStorage handles storage events for cross-tab synchronization.
  • Optionally, the storage has a delayInit (boolean) property, which can be used to tell jotai whether to use Suspense
    • delayInit: true will not suspend when first reading the value of your atom
    • delayInit: false (default) will suspend when first reading the value of your atom

Server-side rendering

Any JSX markup that depends on the value of a stored atom (e.g., a className or style prop) will use the initialValue when rendered on the server (since localStorage and sessionStorage are not available on the server).

This means that there will be a mismatch between what is originally served to the user's browser as HTML and what is expected by React during the rehydration process if the user has a storedValue that differs from the initialValue.

The suggested workaround for this issue is to only render the content dependent on the storedValue client-side by wrapping it in a custom <ClientOnly> wrapper, which only renders after rehydration. Alternative solutions are technically possible, but would require a brief "flicker" as the initialValue is swapped to the storedValue, which can result in an unpleasant user experience, so this solution is advised.

Deleting an item from storage

For the case you want to delete an item from storage, the atom created with atomWithStorage accepts the RESET symbol on write.

See the following example for the usage:

import { useAtom } from 'jotai'
import { atomWithStorage, RESET } from 'jotai/utils'
const textAtom = atomWithStorage('text', 'hello')
const TextBox = () => {
const [text, setText] = useAtom(textAtom)
return (
<input value={text} onChange={(e) => setText(} />
<button onClick={() => setText(RESET)}>Reset (to 'hello')</button>

React-Native implementation

You can use any library that implements getItem, setItem & removeItem. Let's say you would use the standard AsyncStorage provided by the community.

import { atomWithStorage, createJSONStorage } from 'jotai/utils'
import AsyncStorage from '@react-native-async-storage/async-storage'
const storage = createJSONStorage(() => AsyncStorage)
const content = {} // anything JSON serializable
const storedAtom = atomWithStorage('stored-key', content, storage)