Module

x/jotai/docs/advanced-recipes/atom-creators.mdx

👻 Primitive and flexible state management for React
Go to Latest
File
---title: Atom creatorsnav: 5.02---
## atomWithToggle
> `atomWithToggle` creates a new atom with a boolean as initial state & a setter function to toggle it.This avoids the boilerplate of having to set up another atom just to update the state of the first.
```tsimport { WritableAtom, atom } from 'jotai'
export function atomWithToggle( initialValue?: boolean): WritableAtom<boolean, boolean | undefined> { const anAtom = atom(initialValue, (get, set, nextValue?: boolean) => { const update = nextValue ?? !get(anAtom) set(anAtom, update) }) return anAtom as WritableAtom<boolean, boolean | undefined>}```
An optional initial state can be provided as the first argument.
The setter function can have an optional argument to force a particular state, such as if you want to make a setActive function out of it.
Here is how it's used.
```jsimport { atomWithToggle } from 'XXX'
// will have an initial value set to trueconst isActiveAtom = atomWithToggle(true)```
And in a component:
```jsxconst Toggle = () => { const [isActive, toggle] = useAtom(isActiveAtom) return ( <> <button onClick={() => toggle()}> isActive: {isActive ? 'yes' : 'no'} </button> <button onClick={() => toggle(true)}>force true</button> <button onClick={() => toggle(false)}>force false</button> </> )}```
## atomWithToggleAndStorage
> `atomWithToggleAndStorage` is like [`atomWithToggle`](#atom-with-toggle) but also persist the state anytime it changes in given storage using [`atomWithStorage`](../api/utils.mdx#atom-with-storage).Here is the source:
```tsimport { WritableAtom, atom } from 'jotai'import { atomWithStorage } from 'jotai/utils'
export function atomWithToggleAndStorage( key: string, initialValue?: boolean, storage?: any): WritableAtom<boolean, boolean | undefined> { const anAtom = atomWithStorage(key, initialValue, storage) const derivedAtom = atom( (get) => get(anAtom), (get, set, nextValue?: boolean) => { const update = nextValue ?? !get(anAtom) set(anAtom, update) } ) return derivedAtom}```
And how it's used:
```jsimport { atomWithToggleAndStorage } from 'XXX'
// will have an initial value set to false & get stored in localStorage under the key "isActive"const isActiveAtom = atomWithToggleAndStorage('isActive')```
The usage in a component is also the same as [`atomWithToggle`](#atom-with-toggle).
## atomWithCompare
> `atomWithCompare` creates atom that triggers updates when custom compare function `areEqual(prev, next)` is false.This can help you avoid unwanted re-renders by ignoring state changes that don't matter to your application.
Note: Jotai uses `Object.is` internally to compare values when changes occur. If `areEqual(a, b)` returns false, but `Object.is(a, b)` returns true, Jotai will not trigger an update.
```tsimport { atomWithReducer } from 'jotai/utils'
export function atomWithCompare<Value>( initialValue: Value, areEqual: (prev: Value, next: Value) => boolean) { return atomWithReducer(initialValue, (prev: Value, next: Value) => { if (areEqual(prev, next)) { return prev } return next })}```
Here's how you'd use it to make an atom that ignores updates that are shallow-equal:
```tsimport { atomWithCompare } from 'XXX'import { shallowEquals } from 'YYY'import { CSSProperties } from 'react'
const styleAtom = atomWithCompare<CSSProperties>( { backgroundColor: 'blue' }, shallowEquals)```
In a component:
```jsxconst StylePreview = () => { const [styles, setStyles] = useAtom(styleAtom) return ( <div> <div styles={styles}>Style preview</div> {/* Clicking this button twice will only trigger one render */} <button onClick={() => setStyles({ ...styles, backgroundColor: 'red' })}> Set background to red </button> {/* Clicking this button twice will only trigger one render */} <button onClick={() => setStyles({ ...styles, fontSize: 32 })}> Enlarge font </button> </div> )}```
## atomWithRefresh
> `atomWithRefresh` creates a derived atom that can be force-refreshed, by using> the update function.This is helpful when you need to refresh asynchronous data after performing aside effect.
It can also be used to implement "pull to refresh" functionality.
```tsimport { atom, Getter } from 'jotai'
export function atomWithRefresh<T>(fn: (get: Getter) => T) { const refreshCounter = atom(0) return atom( (get) => { get(refreshCounter) return fn(get) }, (_, set) => set(refreshCounter, (i) => i + 1) )}```
Here's how you'd use it to implement an refresh-able source of data:
```jsimport { atomWithRefresh } from 'XXX'
const postsAtom = atomWithRefresh((get) => fetch('https://jsonplaceholder.typicode.com/posts').then((r) => r.json()))```
In a component:
```jsxconst PostsList = () => { const [posts, refreshPosts] = useAtom(postsAtom) return ( <div> <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> {/* Clicking this button will re-fetch the posts */} <button type="button" onClick={refreshPosts}> Refresh posts </button> </div> )}```
## atomWithListeners
> `atomWithListeners` creates an atom and a hook. The hook can be called to> add a new listener. The hook takes as an argument a callback, and that> callback is called every time the atom's value is set. The hook also> returns a function to remove the listener.This can be useful when you want to create a component that can listen to whenan atom's state changes without having to re-render that component with each ofthose state changes.
```tsimport { useEffect } from 'react'import { atom, Getter, Setter, SetStateAction } from 'jotai'import { useUpdateAtom } from 'jotai/utils'
type Callback<Value> = ( get: Getter, set: Setter, newVal: Value, prevVal: Value) => void
export function atomWithListeners<Value>(initialValue: Value) { const baseAtom = atom(initialValue) const listenersAtom = atom(<Callback<Value>[]>[]) const anAtom = atom( (get) => get(baseAtom), (get, set, arg: SetStateAction<Value>) => { const prevVal = get(baseAtom) set(baseAtom, arg) const newVal = get(baseAtom) get(listenersAtom).forEach((callback) => { callback(get, set, newVal, prevVal) }) } ) const useListener = (callback: Callback<Value>) => { const setListeners = useUpdateAtom(listenersAtom) useEffect(() => { setListeners((prev) => [...prev, callback]) return () => setListeners((prev) => { const index = prev.indexOf(callback) return [...prev.slice(0, index), ...prev.slice(index + 1)] }) }, [setListeners, callback]) } return [anAtom, useListener] as const}```
In a component:
```jsxconst [countAtom, useCountListener] = atomWithListeners(0)
function EvenCounter() { const [evenCount, setEvenCount] = useState(0) useCountListener( useCallback( (get, set, newVal, prevVal) => { // Every time `countAtom`'s value is set, we check if its new value // is even, and if it is, we increment `evenCount`. if (newVal % 2 === 0) { setEvenCount((c) => c + 1) } }, [setEvenCount] ) ) return <>Count was set to an even number {evenCount} times.</>}```