This doc describes jotai/utils
bundle.
Ref: pmndrs#26
import { atom, useAtom } from 'jotai'
import { useUpdateAtom } from 'jotai/utils'
const countAtom = atom(0)
const Counter = () => {
const [count] = useAtom(countAtom)
return <div>count: {count}</div>
}
const Controls = () => {
const setCount = useUpdateAtom(countAtom)
const inc = () => setCount((c) => c + 1)
return <button onClick={inc}>+1</button>
}
https://codesandbox.io/s/react-typescript-forked-3q11k
Ref: pmndrs#212
import { atom, Provider, useAtom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
const countAtom = atom(0)
const Counter = () => {
const setCount = useUpdateAtom(countAtom)
const count = useAtomValue(countAtom)
return (
<>
<div>count: {count}</div>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
)
}
https://codesandbox.io/s/react-typescript-forked-1x90m
Ref: pmndrs#41
function atomWithReset<Value>(
initialValue: Value
): WritableAtom<Value, SetStateAction<Value> | typeof RESET>
Creates an atom that could be reset to its initialValue
with
useResetAtom
hook. It works exactly the same
way as primitive atom would, but you are also able to set it to a special value
RESET
. See examples in Resettable atoms.
import { atomWithReset } from 'jotai/utils'
const dollarsAtom = atomWithReset(0)
const todoListAtom = atomWithReset([
{ description: 'Add a todo', checked: false },
])
function useResetAtom<Value>(
anAtom: WritableAtom<Value, typeof RESET>
): () => void | Promise<void>
Resets a Resettable atom to its initial value.
import { useResetAtom } from 'jotai/utils'
import { todoListAtom } from './store'
const TodoResetButton = () => {
const resetTodoList = useResetAtom(todoListAtom)
return <button onClick={resetTodoList}>Reset</button>
}
Ref: pmndrs#217
const RESET: unique symbol
Special value that is accepted by Resettable atoms
created with atomWithReset
or writable atom created
with atom
if it accepts RESET
symbol.
import { atom } from 'jotai'
import { atomWithReset, useResetAtom, RESET } from 'jotai/utils'
const dollarsAtom = atomWithReset(0)
const centsAtom = atom(
(get) => get(dollarsAtom) * 100,
(get, set, newValue: number | typeof RESET) =>
set(dollarsAtom, newValue === RESET ? newValue : newValue / 100)
)
const ResetExample: React.FC = () => {
const setDollars = useUpdateAtom(dollarsAtom)
const resetCents = useResetAtom(centsAtom)
return (
<>
<button onClick={() => setDollars(RESET)}>Reset dollars</button>
<button onClick={resetCents}>Reset cents</button>
</>
)
}
import { atom } from 'jotai'
import { useReducerAtom } from 'jotai/utils'
const countReducer = (prev, action) => {
if (action.type === 'inc') return prev + 1
if (action.type === 'dec') return prev - 1
throw new Error('unknown action type')
}
const countAtom = atom(0)
const Counter = () => {
const [count, dispatch] = useReducerAtom(countAtom, countReducer)
return (
<div>
{count}
<button onClick={() => dispatch({ type: 'inc' })}>+1</button>
<button onClick={() => dispatch({ type: 'dec' })}>-1</button>
</div>
)
}
https://codesandbox.io/s/react-typescript-forked-eg0mw
Ref: pmndrs#38
import { atomWithReducer } from 'jotai/utils'
const countReducer = (prev, action) => {
if (action.type === 'inc') return prev + 1
if (action.type === 'dec') return prev - 1
throw new Error('unknown action type')
}
const countReducerAtom = atomWithReducer(0, countReducer)
https://codesandbox.io/s/react-typescript-forked-g3tsx
Ref: pmndrs#352
This is a function to create a primitive atom. Its default value can be specified with a read function instead of a static initial value.
import { atomWithDefault } from 'jotai/utils'
const count1Atom = atom(1)
const count2Atom = atomWithDefault((get) => get(count1Atom) * 2)
https://codesandbox.io/s/react-typescript-forked-unfro
Ref: pmndrs#23
atomFamily(initializeAtom, areEqual): (param) => Atom
This will create a function that takes param
and returns an atom.
If it's already created, it will return it from the cache.
initializeAtom
is function that can return any kind of atom (atom()
, atomWithDefault()
, ...).
Note that areEqual
is optional, which tell
if two params are equal (defaults to Object.is
).
To reproduce the similar behavior to Recoil's atomFamily/selectorFamily,
specify a deepEqual function to areEqual
. For example:
import { atom } from 'jotai'
import deepEqual from 'fast-deep-equal'
const fooFamily = atomFamily((param) => atom(param), deepEqual)
import { atom } from 'jotai'
import { atomFamily } from 'jotai/utils'
const todoFamily = atomFamily((name) => atom(name))
todoFamily('foo')
// this will create a new atom('foo'), or return the one if already created
import { atom } from 'jotai'
import { atomFamily } from 'jotai/utils'
const todoFamily = atomFamily((name) =>
atom(
(get) => get(todosAtom)[name],
(get, set, arg) => {
const prev = get(todosAtom)
return { ...prev, [name]: { ...prev[name], ...arg } }
}
)
)
import { atom } from 'jotai'
import { atomFamily } from 'jotai/utils'
const todoFamily = atomFamily(
({ id, name }) => atom({ name }),
(a, b) => a.id === b.id
)
https://codesandbox.io/s/react-typescript-forked-8zfrn
Ref: pmndrs#36
function selectAtom<Value, Slice>(
anAtom: Atom<Value>,
selector: (v: Value) => Slice,
equalityFn: (a: Slice, b: Slice) => boolean = Object.is
): Atom<Slice>
This function creates a derived atom whose value is a function of the original atom's value,
determined by selector.
The selector function runs whenever the original atom changes; it updates the derived atom
only if equalityFn
reports that the derived value has changed.
By default, equalityFn
is reference equality, but you can supply your favorite deep-equals
function to stabilize the derived value where necessary.
const defaultPerson = {
name: {
first: 'Jane',
last: 'Doe',
},
birth: {
year: 2000,
month: 'Jan',
day: 1,
time: {
hour: number,
minute: number,
},
},
}
// Original atom.
const personAtom = atom(defaultPerson)
// Tracks person.name. Updated when person.name object changes, even
// if neither name.first nor name.last actually change.
const nameAtom = selectAtom(personAtom, (person) => person.name)
// Tracks person.birth. Updated when year, month, day, hour, or minute changes.
// Use of deepEquals means that this atom doesn't update if birth field is
// replaced with a new object containing the same data. E.g., if person is re-read
// from a database.
const birthAtom = selectAtom(personAtom, (person) => person.birth, deepEquals)
Ref: https://codesandbox.io/s/react-typescript-forked-8czek
Ref: pmndrs#60
useAtomCallback(
callback: (get: Getter, set: Setter, arg: Arg) => Result
): (arg: Arg) => Promise<Result>
This hook allows to interact with atoms imperatively. It takes a callback function that works like atom write function, and returns a function that returns a promise.
The callback to pass in the hook must be stable (should be wrapped with useCallback).
import { useEffect, useState, useCallback } from 'react'
import { Provider, atom, useAtom } from 'jotai'
import { useAtomCallback } from 'jotai/utils'
const countAtom = atom(0)
const Counter = () => {
const [count, setCount] = useAtom(countAtom)
return (
<>
{count} <button onClick={() => setCount((c) => c + 1)}>+1</button>
</>
)
}
const Monitor = () => {
const [count, setCount] = useState(0)
const readCount = useAtomCallback(
useCallback((get) => {
const currCount = get(countAtom)
setCount(currCount)
return currCount
}, [])
)
useEffect(() => {
const timer = setInterval(async () => {
console.log(await readCount())
}, 1000)
return () => {
clearInterval(timer)
}
}, [readCount])
return <div>current count: {count}</div>
}
https://codesandbox.io/s/react-typescript-forked-6ur43
import { atom } from 'jotai'
import { freezeAtom } from 'jotai/utils'
const countAtom = freezeAtom(atom(0))
freezeAtom
take an existing atom and return a new derived atom.
The returned atom is "frozen" which means when you use the atom
with useAtom
in components or get
in other atoms,
the atom value will be deeply freezed with Object.freeze.
It would be useful to find bugs where you accidentally tried
to mutate objects which can lead to unexpected behavior.
import { atomFrozenInDev as atom } from 'jotai/utils'
const countAtom = atom(0)
atomFrozenInDev
is another function to create a frozen atom.
The atom is frozen only in the development mode.
In production, it works as the normal atom
.
The splitAtom
utility is useful for when you want to get an atom for each element in a list.
It works for read/write atoms that contain a list. When used on such an atom, it returns an atom
which itself contains a list of atoms, each corresponding to the respective item in the original list.
A simplified type signature would be:
type SplitAtom = <Item>(arrayAtom: PrimitiveAtom<Array<Item>>): Atom<Array<PrimitiveAtom<Item>>>
Additionally, the atom returned by splitAtom
contains a removal function in the write
direction,
this is useful for when you want a simple way to remove each element in the original atom.
See the below example for usage.
https://codesandbox.io/s/react-typescript-forked-7nir9?file=/src/App.tsx
import * as React from 'react'
import { Provider, atom, useAtom, PrimitiveAtom } from 'jotai'
import { splitAtom } from 'jotai/utils'
import './styles.css'
const initialState = [
{
task: 'help the town',
done: false,
},
{
task: 'feed the dragon',
done: false,
},
]
const todosAtom = atom(initialState)
const todoAtomsAtom = splitAtom(todosAtom)
const TodoList = () => {
const [todoAtoms, removeTodoAtom] = useAtom(todoAtomsAtom)
return (
<ul>
{todoAtoms.map((todoAtom) => (
<TodoItem todoAtom={todoAtom} remove={() => removeTodoAtom(todoAtom)} />
))}
</ul>
)
}
type TodoType = typeof initialState[number]
const TodoItem = ({
todoAtom,
remove,
}: {
todoAtom: PrimitiveAtom<TodoType>
remove: () => void
}) => {
const [todo, setTodo] = useAtom(todoAtom)
return (
<div>
<input
value={todo.task}
onChange={(e) => {
setTodo((oldValue) => ({ ...oldValue, task: e.target.value }))
}}
/>
<input
type="checkbox"
checked={todo.done}
onChange={() => {
setTodo((oldValue) => ({ ...oldValue, done: !oldValue.done }))
}}
/>
<button onClick={remove}>remove</button>
</div>
)
}
const App = () => (
<Provider>
<TodoList />
</Provider>
)
export default App
Sometimes you have multiple async atoms in your components:
const dogsAtom = atom(async (get) => {
const response = await fetch('/dogs')
return await response.json()
})
const catsAtom = atom(async (get) => {
const response = await fetch('/cats')
return await response.json()
})
const App = () => {
const [dogs] = useAtom(dogsAtom)
const [cats] = useAtom(catsAtom)
// ...
}
However, this will start fetching one at the time, which is not optimal - It would be better if we can start fetching both as soon as possible.
The waitForAll
utility is a concurrency helper, which allows us to evaluate multiple async atoms:
const dogsAtom = atom(async (get) => {
const response = await fetch('/dogs')
return await response.json()
})
const catsAtom = atom(async (get) => {
const response = await fetch('/cats')
return await response.json()
})
const App = () => {
const [[dogs, cats]] = useAtom(waitForAll([dogsAtom, catsAtom]))
// or ...
const [dogs, cats] = useAtomValue(waitForAll([dogsAtom, catsAtom]))
// ...
}
You can also use waitForAll
inside an atom - It's also possible to name them for readability:
const dogsAtom = atom(async (get) => {
const response = await fetch('/dogs')
return await response.json()
})
const catsAtom = atom(async (get) => {
const response = await fetch('/cats')
return await response.json()
})
const animalsAtom = atom((get) => {
return get(
waitForAll({
dogs: dogsAtom,
cats: catsAtom,
})
)
})
const App = () => {
const [{ dogs, cats }] = useAtom(animalsAtom)
// or ...
const { dogs, cats } = useAtomValue(animalsAtom)
// ...
}
https://codesandbox.io/s/react-typescript-forked-krwsv?file=/src/App.tsx