Async
All atoms support async behavior such as async read or async write. However there are APIs for more control described here.
loadable
If you don't want async atoms to suspend or throw to an error boundary (for example, for finer-grained control of loading and error logic), you can use the loadable
util.
It would work the same way for any atom. Simply wrap your atoms with the loadable
util. It returns a value with one of three states: loading
, hasData
and hasError
.
{state: 'loading' | 'hasData' | 'hasError',data?: any,error?: any,}
import { loadable } from "jotai/utils"const asyncAtom = atom(async (get) => ...)const loadableAtom = loadable(asyncAtom)// Does not need to be wrapped by a <Suspense> elementconst Component = () => {const [value] = useAtom(loadableAtom)if (value.state === 'hasError') return <Text>{value.error}</Text>if (value.state === 'loading') {return <Text>Loading...</Text>}console.log(value.data) // Results of the Promisereturn <Text>Value: {value.data}</Text>}
atomWithObservable
Ref: https://github.com/pmndrs/jotai/pull/341
Usage
import { useAtom } from 'jotai'import { atomWithObservable } from 'jotai/utils'import { interval } from 'rxjs'import { map } from 'rxjs/operators'const counterSubject = interval(1000).pipe(map((i) => `#${i}`))const counterAtom = atomWithObservable(() => counterSubject)const Counter = () => {const [counter] = useAtom(counterAtom)return <div>count: {counter}</div>}
The atomWithObservable
function creates an atom from a rxjs (or similar) subject
or observable
.
Its value will be last value emitted from the stream.
To use this atom, you need to wrap your component with <Suspense>
. Check out guides/async.
Initial value
atomWithObservable
takes second optional parameter { initialValue }
that allows to specify initial value for the atom. If initialValue
is provided then atomWithObservable
will not suspend and will show initial value before receiving first value from observable. initialValue
can be either a value or a function that returns a value
const counterAtom = atomWithObservable(() => counterSubject, {initialValue: 10,})const counterAtom2 = atomWithObservable(() => counterSubject, {initialValue: () => Math.random(),})
Codesandbox
unwrap
The unwrap
util will convert an async atom to a sync atom like loadable
.
Unlike loadable
, the fallback value can be configured.
Unlike loadable
, the error won't be handled and just thrown.
The use case of unwrap
is to ease deriving atoms.
This is especially useful for v2 API,
because get
in the read function doesn't resolve promises.
Signature
function unwrap<Value, Args extends unknown[], Result>(anAtom: WritableAtom<Value, Args, Result>,): WritableAtom<Awaited<Value> | undefined, Args, Result>function unwrap<Value, Args extends unknown[], Result, PendingValue>(anAtom: WritableAtom<Value, Args, Result>,fallback: (prev?: Awaited<Value>) => PendingValue,): WritableAtom<Awaited<Value> | PendingValue, Args, Result>function unwrap<Value>(anAtom: Atom<Value>): Atom<Awaited<Value> | undefined>function unwrap<Value, PendingValue>(anAtom: Atom<Value>,fallback: (prev?: Awaited<Value>) => PendingValue,): Atom<Awaited<Value> | PendingValue>
Usage
import { atom } from 'jotai'import { unwrap } from 'jotai/utils'const countAtom = atom(0)const delayedCountAtom = atom(async (get) => {await new Promise((r) => setTimeout(r, 500))return get(countAtom)})const unwrapped1Atom = unwrap(delayedCountAtom)// The value is `undefined` while pendingconst unwrapped2Atom = unwrap(delayedCountAtom, (prev) => prev ?? 0)// The value is `0` initially, and subsequent updates keep the previous value.