-
The problemAtoms for query/data management libraries (e.g. const userAtom: Atom<User | Promise<User>> = ...; If no data is cached yet, the atom holds a Promise, but if the data is already there and an optimistic update was performed, the atom's data is updated in sync and the atom can hold a concrete value instead of a Promise. This dual nature is nice (concrete value or a promise) because derived atoms could process the value in sync if it is available, and fallback to awaiting and returning a Promise if it is not. However, handling those cases looks to be more trouble that it is worth, so the easiest is to just always await. const userAtom: Atom<User | Promise<User>> = ...;
// typeof uppercaseNameAtom is Atom<Promise<String>>
const uppercaseNameAtom = atom(async (get) => {
const user = await get(userAtom);
return user.name.toUppercase();
}); That is what I have done in my code as well, but because of that, the parts of the app that use any atom that derives A potential solutionBelow is an atom that handles both cases separately (processes the data in sync if it is available, returns a promise otherwise). const userAtom: Atom<User | Promise<User>> = ...;
// typeof uppercaseNameAtom is Atom<string | Promise<String>>, which means we can derive it
// again and get the same dual behavior.
const uppercaseNameAtom = atom((get) => {
const process = (user: User) => user.name.toUppercase();
const userOrPromise = get(userAtom);
if (isPromise(userOrPromise)) {
// data not available yet, process it asynchronously
return userOrPromise.then(process);
}
// data is here, process it now
return process(userOrPromise);
}); It can be a bit too verbose though, and tougher to do more complex processing with. Let's try to simplify this a little. Say we have a function type PromiseOrValue<T> = T | Promise<T>;
function soon<T, R>(data: PromiseOrValue<T>, process: (data: T) => R): PromiseOrValue<R> {
// run `process` synchronously if `data` is known, return promise otherwise.
} const userAtom: Atom<User | Promise<User>> = ...;
// typeof uppercaseNameAtom is Atom<string | Promise<String>>
const uppercaseNameAtom = atom(
(get) => soon(get(userAtom), (user) => user.name.toUppercase())
); For multiple dependencies, I made a Since most of my derived atoms were just a few sync/async dependencies that were transformed in some way, I wanted to create a simple helper to reduce the amount of boilerplate I had to write: const userAtom: Atom<User | Promise<User>> = ...;
// typeof uppercaseNameAtom is Atom<string | Promise<String>>
const uppercaseNameAtom = derive([userAtom], (user) => user.name.toUppercase()); Or an example with multiple dependencies: const usersAtom: Atom<User[] | Promise<User[]>> = ...;
const nameFilterAtom = atom('');
// typeof filteredUsersAtom is Atom<User[] | Promise<User[]>>
const filteredUsersAtom = derive(
[usersAtom, nameFilterAtom],
(users, nameFilter) => {
if (!nameFilter) {
return users;
}
return users.filter((user) => user.name.includes(nameFilter));
}
); Potential enhancementsSo far I explored making the ConclusionThis has thus far been a real help in my codebase, decreasing redundant computations. If this is too specific to be included in the library, I would be happy to create a separate module, maybe |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 3 replies
-
Thanks for opening up a discussion.
Yes, this is pretty interesting. I knew this was possible and thought it's kind of advanced usage.
Exactly.
Exactly.
I'm so glad to see you've reached there. I think I did something similar somewhere.
Niceeee.
I'm very excited to see your solution. |
Beta Was this translation helpful? Give feedback.
-
I quite frequently do these things within my own applications but haven't yet attempted to generalise and abstract this out in the way you have. I have I agree that there should be a nicer way of doing these things, but some of this API I find kind of unusual. Like, calling a function And then internally we have I don't have a good name for either right now though. It's kind of like a Or maybe another way of looking at it, is that it could be called something like |
Beta Was this translation helpful? Give feedback.
-
Hi all, I'd like to share my use case since I'm also struggling with potentially async values. I'm writing a mobile app using react-native and expo-sqlite as my database. Reads and writes are async. My atoms typically look like so: export const fooAtom = atomWithStorage(
'foo',
Promise.resolve(undefined as string | undefined), // trying to get the correct type inference here 🤔 🤷♂️
{
getItem: async () => await db.query.fooTable.findFirst() // some query,
setItem: async (key, newFoo) => {
await db.update() // some updater
},
removeItem: async () => {
await db.delete() // some deleter
},
subscribe: // some subscriber with DB-listener
},
{ getOnInit: true },
) For now, I simply Looking forward to future updates here 🥳 |
Beta Was this translation helpful? Give feedback.
Thanks for opening up a discussion.
Yes, this is pretty interesting. I knew this was possible and thought it's kind of advanced usage.
Exactly.
Exactly.
I'm so glad to see you've reached there. I thin…