Recoil: How to refresh/invalidate an asynchronous selector?
Selectors can be used to asynchronously fetch data from an API. But how is it possible to trigger a re-fetch of this data?
Given this selector:
const todosState = selector({
key: "todosState",
get: async () => {
const result = await fetch("https://example.com/todos");
const todos = await result.json();
return todos;
},
});
In a scenario where a user wanted to reload his todos, because he knows his coworker added a new todo to the list. How would he trigger this selector to re-fetch the todos from the API?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 16
- Comments: 28 (7 by maintainers)
@acutmore Thanks for your suggestion. Although your code works as intended, it feels more like a workaround instead of an official solution. I genuinely thought this update could be triggered using the
useResetRecoilState()
function.An obvious way to make this less of a workaround would be to simply use an atom and handle the update manually. However, I really like the concept of the Data Flow Graph mentioned in the doc, which makes data fetching really declarative and implicit for the consumer of the selector.
How about adding a re-evaluate feature to the API, either with
useResetRecoilState()
or with a new function in the selector options?Hi @philippta. I agree, does feels more like a workaround. Maybe someone from the Recoil team will have a more official solution.
I had another go using
useResetRecoilState
. Callingreset
sets an Atom’s value back to thedefault: ...
value in the Atom’s config, or calls a selector’sset
passing in Recoil’sDefaultValue
. I had a go and selectors do not seem to support async set. So this gets back to synchronously updating an atom to force a selector’s async get to re-run.Hi @philippta. Selectors are re-run when an atom/selector it uses (depends on) changes. To force a selector to update you could do something like this:
Is this still the recommended approach? Still feels like a workaround. 😕We’re just trying out Recoil with a new project at the moment and we already have 3 of those triggers, which are becoming kind of unwieldy (even with calling them in the setter).
Having an API to re-run a selector would make more sense in my opinion. Something like an extension of the previously mentioned
useResetRecoilState
. That way the operation would be tied to a specific selector directly, likeuseResetRecoilState(mySelector)
, and not indirectly via auseSetRecoilState(mySelectorTrigger)
or an implementation detail we need to add to the setter ourselves.Since the library is pretty new and it’s our first time using it, maybe we’re also just using it incorrectly… Let’s imagine we have a table of entries, to which we like to apply filters. Our filters are atoms. We fetch the entries with an async selector and then
get
the entries and the filters in a separate selector, where we apply the filters to the entries. Then we use thisfilteredEntriesSelector
in our component. This works perfect, until the user navigates away from the page (we use react-router, the page is not refreshed) and then back to the page again. Now he is being shown stale data, since the selector is not re-run again. Currently we solve this by running the trigger workaround, when the user clicks on the button that navigates to the page.Are we doing this correctly and recoil is limited in that regard or are we using it incorrectly?
Atoms can currently have a default value that is an async Promise, however Promises are a one-shot concept for resolving to a value or error. You can reset an atom and it will revert to the value of that original default promise, either pending, resolved, or error. One thing I’m working on is working through a proposal to be able to subsequently set atoms to a new promise, so they could be used to store the results of a new query refresh by explicitly setting them to a new promise. So, instead of using “reset” you would set it to a promise for the new query.
Totally agree with @tobias-tengler!
useSetRecoilState
for selectors is what recoil lacks to be most perfect state management library. Recoil is already a beauty silver bullet, but using a trigger atom looks like a weird safety catch.Selector refresher is now part of unstable API.
Maybe recoil can take some inspiration from ReactQuery, I’m not familiar with the details under the hood of both libraries but both Recoil and ReactQuery have “keys” ReactQuery uses them to invalidate queries and they get re-fetched automatically, maybe we can have something like this in recoil
invalidateAtoms('Key')
simlar to the ReactQueryinvalidateQueries('Key')
Yes I’ve double-checked. It works on React Native too. The problem is I have one redundant line
const userData = useRecoilState(UserData)
outside of theUserInfo
component on the very top of the messy code. It works on the first rendering becauseuserData
already has a value that was set by the parent screen. When reloading, theconst userData = useRecoilState(UserData)
is really outside of Suspense. Thanks for your time.Hi @atulmy. The warning you are seeing is a known issue, more details here: #12. For now it should be safe to ignore it.
I’m facing following warning when using above described method to force cache update:
Any idea what may be going wrong?
@acutmore 's example is exactly what I was going to suggest. Good work!
Good question. I do not know. I raised #51 to ask about how cancelation might work.
@acutmore How would you abort the fetch on unmount in this example?