Async atom that suspends the first time, returns previous value subsquently #2228
-
My team is trying to create an async atom that suspends the first time while the initial value is being loaded but subsequently returns the previous value synchronously when new values are being loaded. For example: const values = ["first-value", "second-value"];
const syncAtom = atom(0);
const swrAtom = selectAtomWithSwr(async (get) => values[get(syncAtom) % 2]); We want Here is a CodeSandbox demonstrating what we are trying (but failing) to do. The Suspense loading fallback prints a message every time it renders. We want to see that message just once upon initial load, but we are seeing it every time the state changes and a new async request is made: |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
It's tricky, but should be possible. Maybe, use |
Beta Was this translation helpful? Give feedback.
-
I made updated version of atomWithSwr (stail-while-revalitade + writeable support) thanks to @scamden and @dai-shi this version allows base atom has import { atom } from 'jotai';
import type { Atom, Getter, WritableAtom } from 'jotai';
import { unwrap } from 'jotai/utils';
/**
* A symbol value that indicates there is no previously resolved value.
* We use a unique symbol because values like undefined or null may already be used
* by the original atom.
* ref) https://x.com/dai_shi/status/1722818712267583924/photo/1
*/
const EMPTY = Symbol();
/**
* Wraps the given asynchronous atom (asyncAtom) so that it returns the previously resolved value if it exists;
* if not, it reads the asyncAtom's promise and suspends.
*
* @param baseAtom An atom capable of asynchronous handling (value or Promise<T>)
* @returns An atom that immediately returns the previous value if available, or reads the asyncAtom to suspend when not.
*/
export function atomWithSwr<T, Args extends Array<any>, Result>(
baseAtom: WritableAtom<Promise<T>, Args, Result>
): WritableAtom<T | Promise<T>, Args, Result>;
export function atomWithSwr<T>(baseAtom: Atom<Promise<T>>): Atom<T | Promise<T>>;
export function atomWithSwr<T>(
baseAtom: Atom<Promise<T>> | WritableAtom<Promise<T>, Array<any>, any>
) {
/**
* The unwrap() function creates a synchronous atom that stores the last resolved value of the asyncAtom.
* However, since the original atom might return undefined, we return EMPTY when the asyncAtom has not yet resolved.
*/
const unwrappedAtom = unwrap(baseAtom, (...args) =>
args.length ? (args[0] as Awaited<T>) : EMPTY
);
/**
* If a previous value exists, return it immediately; otherwise, read the asyncAtom's value and suspend.
*/
const read = (get: Getter) => {
if (get(unwrappedAtom) === EMPTY) {
return get(baseAtom);
}
return get(unwrappedAtom);
};
return 'write' in baseAtom ? atom(read, baseAtom.write) : atom(read);
} |
Beta Was this translation helpful? Give feedback.
Thanks
unwrap
as you described did the trick! It's much simpler now, and works:Here's the important part: