question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Prefetch and cache chained async state?

See original GitHub issue

Hello, I’m using useRecoilValueLoadable with async selectors, and have a chain of selectors that look something like this:

atom --> async selector (user) --> async selector (mother)
                                          \
                                           --> async selector (father)

I would like to prefetch / cache all three async selectors in my main page using useRecoilCallback(). I am having a hard time figuring how to pre-fetch / cache all three fetch calls. The “user” selector is never cached until I load all of the leaf views (“father” and “mother”). Depending on which leaf view I render first, the other leaf selector will make an unnecessary fetch call when rendered. The first async selector in the middle is almost never cached until I render both views. Below is an example of the code. I tried using a combination of useEffect(), useRecoilCallback({snapshot, set}) with intermediate synchronous atoms for state, but no luck at all. The below code resembles the furthest I have gotten so far. The only thing I can think of is to add my own caching mechanism in the “fetch” call, which seems to defeat the purpose of selectors. I would appreciate any help!

export const userIdState = atom({
  key: "userIdState",
  default: ""
});

export const userQuery = selector({
  key: "userQuery",
  get: async ({ get }) => {
    const { data } = await fetchUser(get(userIdState));
    return data;
  }
});

export const fatherDetailsQuery = selector({
  key: "fatherDetailsQuery",
  get: async ({ get }) => {
    const { fatherId } = await get(userQuery);
    const { data } = await fetchFather(fatherId);
    return data;
  }
});

export const motherDetailsQuery = selector({
  key: "motherDetailsQuery",
  get: async ({ get }) => {
    const { motherId } = await get(userQuery);
    const { data } = await fetchMother(motherId);
    return { data };
  }
});

function MyPage = () => {
  useRecoilCallback(({ snapshot }) => async () => {
    // this is the only way I can seem to get some caching of mother/father loadables
    await snapshot.getPromise(userQuery); 
    snapshot.getLoadable(fatherDetailsQuery);
    snapshot.getLoadable(motherDetailsQuery);
  })();
  return (
    <div>
       // render user stuff
       // assume showMother and showFather are false and can be toggled true/false
       {showMother && <Mother />}
       {showFather && <Father />}
    </div>
  );
}

function Father = () => {
  const fatherLoadable = useRecoilValueLoadable(fatherDetailsQuery);
  // render father stuff
}

function Mother = () => {
   const motherLoadable = useRecoilValueLoadable(motherDetailsQuery);
   // render mother stuff
}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

6reactions
csantos42commented, Jun 25, 2020

hey @dliu120 the bad news is that caching of async selectors (specifically, caching of async selectors that are part of async chains like the one you’ve described above) is currently buggy and not working as expected.

The good news is that we are aware of this issue and have been working on a solution for some time now. We ended up rewriting the implementation of async selectors entirely to account for cases like this.

The gist of the rewrite is that every time a selector’s value is requested, we store the chunk of state that the selector was called with, so that if the selector is requested again, we can check to see if the selector is already running against that state given the dependencies that have been discovered so far. If it has, we don’t rerun the selector.

Unfortunately the only foolproof way around this for the moment is as you suggested: implementing caching at the fetch layer.

This fix is very high on our priorities so hopefully will be out soon. I’ll be sure to link this issue to the corresponding PR/issue when it is opened.

Thanks for opening this and sorry for the trouble!

2reactions
csantos42commented, Jun 25, 2020

@calumjames exactly! You could even fetch all 3 in parallel and use the waitForAll() utility to wait for them (or just use snapshot.getLoadable() sequentially for the same effect if you don’t care about waiting for them):

await snapshot.getPromise(
  waitForAll([
    userQuery,
    fatherDetailsQuery,
    motherDetailsQuery,
  ]),
);

Recoil will be smart enough to prevent running a selector unnecessarily more than once, so even if userQuery and fatherDetailsQuery both call motherDetailsQuery, motherDetailsQuery will only run once

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mixing Promises And async / await For Caching Purposes In ...
Ben Nadel demonstrates how to Promise objects with async and await in order to create a recursive caching mechanism in JavaScript.
Read more >
Fetch, Cache, Prefetch data using React Query | Akash Devgan
In this video, we will look into what is React Query and how you can use it in your next React project.
Read more >
Asynchronous Memory Access Chaining - VLDB Endowment
State -of-the-art software prefetching approaches for database systems work by arranging a set of independent lookups into a group (Group Prefetching [8]) or ......
Read more >
Data Prefetch Mechanisms - ACM Digital Library
Data prefetching has been proposed as a technique for hiding the access latency of data referencing patterns that defeat caching strategies. Rather than...
Read more >
React-Query: You Might Not Need State Management ‍♂️
It's server-state, able to manage cache data, asynchronously update stale data in the background. It might be a good replacement for global ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found