-
-
Notifications
You must be signed in to change notification settings - Fork 658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
async set atom remains cached #521
Comments
Sorry for the confusion. Lines 440 to 441 in 39bc8b3
It's prohibited now. You don't get this error? The canonical pattern is to use two atoms. Or, can you try |
Ouch :( No such warning on my side. If this really is prohibited now it would require a tremendous refactoring on our end. Do you think there might be a workaround? What would be the actual pattern with Thank you for responding so quickly! |
Actually, atomWithDefault is implemented with 2 atoms internally. import { atomWithDefault } from 'jotai/utils';
export const someAsyncAtom = atomWithDefault(
async (get) => {
const {data} = await getData();
return data;
},
async (get, set, update) => {
if (update) {
set(someAsyncAtom, update);
} else {
const {data} = await getData();
set(someAsyncAtom, data);
}
}
); this is supposed to work. |
How stable is the Moreover I am still trying to understand the actual logic of Thank you so much for the amazing work and congrats on |
I think it's complete. Please check the source code if you are interested in how it's implemented. It works like PrimitiveAtom, but the initial value is specified by Yeah, I'd rather encourage reading the code, and you might have an idea for some variants for your purpose. |
Ok I will have a look at the implementation of |
When I try to implement it like this I first get a warning in my IDE that |
Oops, that was my bad. So, it has to be something like this. const baseAtom = atomWithDefault(
async (get) => {
const {data} = await getData();
return data;
},
)
export const someAsyncAtom = atomWithDefault(
(get) => get(baseAtom),
async (get, set, update) => {
if (update) {
set(baseAtom, update);
} else {
const {data} = await getData();
set(baseAtom, data);
}
}
); It may not seem ideal, but does this at least work? |
Don't you mean to use a normal |
Ok so I tested it with const baseAtom = atomWithDefault(
async (get) => {
const {data} = await getData();
return data;
},
)
export const someAsyncAtom = atom(
(get) => get(baseAtom),
async (get, set, update) => {
if (update) {
set(baseAtom, update);
} else {
const {data} = await getData();
set(baseAtom, data);
}
}
); However we have 30+ such cases scattered across our code base, I really can't have a duplication of all those atoms :( Is there any other more elegant way that we could keep it backwards compatible? |
Well, I mean it's composable. You can create your own util on your end. const atomWithDefaultAndWrite = (read, write) => {
const baseAtom = atomWithDefault(read)
const derivedAtom = atom((get) => get(baseAtom), write)
return derivedAtom
} Oh, this doesn't work... const atomWithGetData = (getData) => {
const baseAtom = atomWithDefault(
async (get) => {
const {data} = await getData()
return data
},
)
const derivedAtom = atom(
(get) => get(baseAtom),
async (get, set, update) => {
if (update) {
set(baseAtom, update)
} else {
const {data} = await getData()
set(baseAtom, data)
}
},
)
return derivedAtom
} I hope this works for you with this pattern. |
Ok I see your point. I think however that it would be nice if there was a kind of "out-of-the-box" solution for this use case, as I reckon it might be quite frequent. Just my 2C ;) I will try your approach with the custom solution and report back. Many thanks! |
Yeah, after writing the last snippet, I felt the same. This use case is slightly different from |
cc @aulneau |
Ok so I took a look at our current atoms and the most frequent use case is as follows:
Basically this is very similar to the functions offered by I think it would be very nice to have something like const [data,mutate,refresh] = useAsyncAtom(someAsyncAtom)
// here you have access to the data
data.map(d=>d.title)
// here you can manually mutate the data if needed. This wouldn't trigger suspense for example
mutate([{name:1,title:1},{name:2,title:2},{name:3,title:3}])
// here you can manually invoke a refresh. I would expose this part explicitly in the definition of the atom so that the refresh part can be customized
refresh() What do you think about this? |
I think the implementation will be the combination of atomWithReset and atomWithDefault. I'm not sure what it should be called but the implementation is something like this: import { atom } from 'jotai'
import type { Atom, PrimitiveAtom, WritableAtom, SetStateAction } from 'jotai'
type Read<Value> = Atom<Value>['read']
export const REFRESH = Symbol()
export function atomWithRefresh<Value>(getValue: Read<Value>) {
type Update = SetStateAction<Value> | typeof REFRESH
const EMPTY = Symbol()
const overwrittenAtom = atom<Value | typeof EMPTY>(EMPTY)
const anAtom = atom<Value, Update>(
(get) => {
const overwritten = get(overwrittenAtom)
if (overwritten !== EMPTY) {
return overwritten
}
return getValue(get)
},
(get, set, update) => {
if (update === REFRESH) {
set(anAtom, getValue(get))
} else {
set(
anAtom,
typeof update === 'function'
? (update as (prev: Value) => Value)(get(anAtom))
: update
)
}
}
)
return anAtom as WritableAtom<Value, Update>
} |
|
The previous one is violating a rule. Here's the fixed one. import { atom } from 'jotai'
import type { Atom, PrimitiveAtom, WritableAtom, SetStateAction } from 'jotai'
type Read<Value> = Atom<Value>['read']
export const REFRESH = Symbol()
export function atomWithRefresh<Value>(getValue: Read<Value>) {
type Update = SetStateAction<Value> | typeof REFRESH
const EMPTY = Symbol()
const overwrittenAtom = atom<Value | typeof EMPTY>(EMPTY)
const anAtom = atom<Value, Update>(
(get) => {
const overwritten = get(overwrittenAtom)
if (overwritten !== EMPTY) {
return overwritten
}
return getValue(get)
},
(get, set, update) => {
if (update === REFRESH) {
set(overwrittenAtom, EMPTY)
} else {
set(
anAtom,
typeof update === 'function'
? (update as (prev: Value) => Value)(get(anAtom))
: update
)
}
}
)
return anAtom as WritableAtom<Value, Update>
} Now, I think this is just an extension to atomWithDefault. |
@dai-shi amazing work here!! Although I have a dependency, an ID basically, which sits in another atom, which I use to grab from my IndexedDB key value store the data in an async fashion. I need to refresh the data when I write to the store, although the ID hasn't changed. Will the above utility react to dependencies tracked in the get function of the async readable atom? |
@LucaColonnello |
@dai-shi sorry to SPAM the issue here, but this is soooo powerful! Question is, am I doing it right? XD |
It looks good. You are not really using the power of atomWithDefault in the example, are you? |
I'll link the document here to save others time 😃 https://jotai.org/docs/advanced-recipes/atom-creators#atom-with-refresh |
I have an async atom that looks like follows
Now up until
v0.16.8
if I wanted to imperatively refresh my data I could use something likeHowever starting with
v.0.16.9
after callingupdateData
every component that hashas now stale data that dates back to before calling the imperative refresh. Any ideas? This is a breaking change for our application.
The text was updated successfully, but these errors were encountered: