Recoil: How to cache values?

Created on 31 Jul 2020  路  5Comments  路  Source: facebookexperimental/Recoil

I'm trying to implement caching: If a value doesn't exist, then fetch it.

const cache = atom({
  key: "cache",
  default: new Map()
});

const value = selectorFamily({
  key: "value",
  get: (id) => async ({ get }) => {
    const map = get(cache);
    return map.get(id) || await fetch(id);
  },
});

My questions are:
1) Can I force a re-fetch without manually fetching and setting in my component? I tried adding a setter:

  set: (id) => async ({ set }) => {
    const data = await fetch(id);
    set(cache, (prevState) => prevState.set(id, data));
  },

with the idea of just calling useResetRecoilState(value(id)). But, this doesn't seem to update my components. Is this because set is async?

2) How do I initially populate the cache? I can call set for the IDs I care about, but ideally I want get to add values to the cache.

question

All 5 comments

JS Map is mutable, so you should not call set() on a Map stored in an atom. Atom state isn't really meant to be used for holding caches like this. It looks like you're trying to use a cache to cache the results of the selectorFamily() evaluation? If so, that shouldn't be necessary since Recoil already caches selector evaluations.

Ok, I can replace the Map with an object.

I want to cache because when the selectorFamily is evaluating, it returns a promise. I'd like to return the previous value if available:

  • On first eval, wait for fetch and suspend
  • On subsequent evals, return cached value while fetching, then return new value

Maybe I need to use another atom?

Using a mutable object instead of a map is still a problem. All values stored in an atom should be immutable / read-only. It also sounds like selectors are already doing the caching that you want. When a selector is evaluated for the first time it runs the evaluation function, which may return a pending promise, and the result is cached. Subsequent attempts to get the selector value will then use that same cached promise which may still be pending, or the cache will be updated when the promise resolves or errors.

What about using an immutable map?

The selector is doing _almost_ what I want. My issue is that my selector is impure - first eval returns 1, next eval may return 2, etc. I need something to store the resolved 1 value and provide it to consumers, while fetching 2. When 2 resolves, it will overwrite 1.

Duplicate of #290

Ah, so your usecase is to return the previous value while pending. the useTransition() hook may be useful for this when Recoil releases full concurrent mode support. In the meantime, #290 discusses some current workarounds.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

polemius picture polemius  路  3Comments

eLeontev picture eLeontev  路  3Comments

yuantongkang picture yuantongkang  路  3Comments

atanasster picture atanasster  路  3Comments

thegauravthakur picture thegauravthakur  路  3Comments