Skip to main content
Module

x/jotai/docs/utilities/storage.mdx

👻 Primitive and flexible state management for React
Go to Latest
File
---title: Storagenav: 3.01keywords: storage,localstorage,sessionstorage,asyncstorage,persist,persistance---
## atomWithStorage
Ref: https://github.com/pmndrs/jotai/pull/394
```jsximport { 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.
### Parameters
**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 the following methods:
- **getItem(key, initialValue)** (required): Reads an item from storage, or falls back to the `intialValue`- **setItem(key, value)** (required): Saves an item to storage- **removeItem(key)** (required): Deletes the item from storage- **subscribe(key, callback, initialValue)** (optional): A method which subscribes to external storage updates.If not specified, the default storage implementation uses `localStorage` for storage/retrieval, `JSON.stringify()`/`JSON.parse()` for serialization/deserialization, and subscribes to `storage` events for cross-tab synchronization.
<CodeSandbox id="vuwi7" />### 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](https://www.joshwcomeau.com/react/the-perils-of-rehydration/#abstractions), 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:
```jsximport { 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(e.target.value)} /> <button onClick={() => setText(RESET)}>Reset (to 'hello')</button> </> )}```
If needed, you can also do conditional resets based on previous value.
This can be particularly useful if you wish to clear keys in localStorage if previous values meet a condition.
Below exemplifies this usage that clears the `visible` key whenever the previous value is `true`.
```jsximport { useAtom } from 'jotai'import { atomWithStorage, RESET } from 'jotai/utils'
const isVisibleAtom = atomWithStorage('visible', false)
const TextBox = () => { const [isVisible, setIsVisible] = useAtom(isVisibleAtom) return ( <> { isVisible && <h1>Header is visible!</h1> } <button onClick={() => setIsVisible((prev) => prev ? RESET : true))}>Toggle visible</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.
```jsimport { atomWithStorage, createJSONStorage } from 'jotai/utils'import AsyncStorage from '@react-native-async-storage/async-storage'
const storage = createJSONStorage(() => AsyncStorage)const content = {} // anything JSON serializableconst storedAtom = atomWithStorage('stored-key', content, storage)```
### Validating stored values
To add runtime validation to your storage atoms, you will need to create a custom implementation of storage.
Below is an example that utilizes Zod to validate values stored in `localStorage` with cross-tab synchronization.
```jsimport { atomWithStorage, createJSONStorage } from 'jotai/utils'import { z } from 'zod'
const myNumberSchema = z.number().int().nonnegative()
const storedNumberAtom = atomWithStorage('my-number', 0, { getItem(key, initialValue) { const storedValue = localStorage.getItem(key) try { return myNumberSchema.parse(JSON.parse(storedValue ?? '')) } catch { return initialValue } }, setItem(key, value) { localStorage.setItem(key, JSON.stringify(value)) }, removeItem(key) { localStorage.removeItem(key) }, subscribe(key, callback, initialValue) { if ( typeof window === 'undefined' || typeof window.addEventListener === 'undefined' ) { return } window.addEventListener('storage', (e) => { if (e.storageArea === localStorage && e.key === key) { let newValue try { newValue = myNumberSchema.parse(JSON.parse(e.newValue ?? '')) } catch { newValue = initialValue } callback(newValue) } }) },})```