'Looking for a pattern to normalize state in Recoil without losing the benefit of Suspense

In RecoilJS, seamless integration with React Suspense for async selectors is a big plus. However, I am running into issues trying to normalize the data cached in Recoil, while still making use of Suspense.

To explain the problem through an example, a User might have a collection of Books. A query populates the collection with a single API call to get all the user's "Favorite" books. A later query might simply request a single book, which may or may not have already been retrieved through the favorite books query.

What I'd like to do it maintain a normalized cache of Books, such as in an AtomFamily keyed by bookId, so I don't have two copies of books that are pulled with different queries. However, I run into a problem, which is that I would like to use Suspense for any one of the queries that retrieves one or more Books. And the natural way to do that with Recoil is to use an async Selector. But I don't see it, if there's a way to normalize the data fetched through async selectors.

Is there a pattern I am overlooking, that would allow me to use async selectors representing different queries that are backed by a shared, normalized AtomFamily?

For example, if I have this BAD code, which creates duplicate objects in my state, how might I rework it to maintain a shared cache for the actual Book objects, and still make use of Suspense if a query is still fetching when a component that uses this state renders?

Query 1: get a group of books through a selector:

const favoriteBooksSelector = selector({
  key: 'MyFavoriteBooks',
  get: async ({ get }) => {
    const response = await allMyFavorityBooksDBQuery({
      userID: get(currentUserIDState)
    });
    return response.books;
  },
});

Query 2: get a single book, looks something like:

  export const singleBookSelector = selectorFamily({
    key: 'singleBookSelector',
    get: (bookId: string) => async ({ get }) => {
        const response = await singleBookDBQuery({
            userID: get(currentUserIDState)
          });
          return response.book;
    }
  });



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source