JotaiJotai

ηŠΆζ…‹
Primitive and flexible state management for React

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> element
const 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 Promise
return <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 pending
const unwrapped2Atom = unwrap(delayedCountAtom, (prev) => prev ?? 0)
// The value is `0` initially, and subsequent updates keep the previous value.