`loadable` API for using async atoms without suspense
See original GitHub issuePrior discussion: https://github.com/pmndrs/jotai/issues/269
Recoil has a useRecoilValueLoadable
for bypassing suspense and returning a “loadable” object. This is useful in some cirumstances when suspense is not desired, and is also much easier than implementing it via the method recommended in the Jotai docs.
No-suspense code example
const fetchResultAtom = atom({ loading: true, error: null, data: null })
const runFetchAtom = atom(
(get) => get(fetchResultAtom),
(_get, set, url) => {
const fetchData = async () => {
set(fetchResultAtom, (prev) => ({ ...prev, loading: true }))
try {
const response = await fetch(url)
const data = await response.json()
set(fetchResultAtom, { loading: false, error: null, data })
} catch (error) {
set(fetchResultAtom, { loading: false, error, data: null })
}
}
fetchData()
}
)
runFetchAtom.onMount = (runFetch) => {
runFetch('https://json.host.com')
}
const Component = () => {
const [result] = useAtom(runFetchAtom)
console.log(result) // { loading: ..., error: ..., data: ... }
return <div>...</div>
}
I’m proposing a read-only loadable
util which returns an atom that converts an async atom into a sync atom, just like Recoil’s loadable utils.
Depending on context, consumers may want errors to still be thrown or captured into the loadable object. There are two ways of handling this.
1. Separate suspendable
/loadable
APIs
const SUSPENDED = Symbol("jotai/atomSuspended");
type Loadable<T, TError = any> = { type: "loading" } | { type: "value", value: T } | { type: "error", value: TError };
// will still throw on error
const suspendable = <T>(atom<Promise<T> | T>) => T | SUSPENDED;
// will not throw on error
const loadable = <T>(atom<Promise<T> | T>) => Loadable<T>;
This has the advantage of calling a single function without parameters whether you want errors to be thrown or not. The suspendable
type signature is also much simpler, as we don’t need to be able to disambiguate between value and error types.
2. Param in the loadable
function
function loadable<Value, Update>(
atom: WritableAtom<Value, Update>
): WritableAtom<{ loading: true; data?: Value } | { data: Value } | { error: unknown }, Update>
function loadable<Value, Update>(
atom: WritableAtom<Value, Update>,
throwsError: boolean
): WritableAtom<{ loading: true; data?: Value } | { data: Value }, Update>
function loadable<Value>(
atom: Atom<Value>
): Atom<{ loading: true; data?: Value } | { data: Value } | { error: unknown }>
function loadable<Value, Update>(
atom: Atom<Value, Update>,
throwsError: boolean
): Atom<{ loading: true; data?: Value } | { data: Value }>
This has the advantage of there only being one function. However, a flag with a default value means one option will be more convenient than another.
Outstanding questions:
- Which API to choose?
- Should this support writable atoms in the first implementation? Or can that wait?
Issue Analytics
- State:
- Created 2 years ago
- Comments:29 (21 by maintainers)
@dai-shi I haven’t forgotten this, just super busy at the moment. If this isn’t done by the time I get some time to do so, I certainly will!
This makes me so happy to see! I think this will enable a lot more usage from folks who maybe were hesitant due to suspense only support.