Search code examples
reactjsrecoiljs

What's the appropriate way load async (over the network) into atomFamily/selectorFamily recoil component?


I'm researching how to use RecoilJS in my application, and I like the concept of an atomFamily - which seems like a great way to keep track of individual items in a list while keeping the app performant, due to each item's updates not forcing a refresh of the entire list.

I want to load a set of data, let's say items of a todo list (to give a canonical example) from a remote server in an async way and then initialize the atomFamily and create a react component from each of these items.

My first thought is to create a useEffect hook. In it I'd fetch the data, then run a for-loop and call atomFamily to create atoms for each each of the items.

But then I started reading about selectorFamily - it sounds like that may be the right approach? Also, there are atom effects (unstable) that look like they might be the correct way to do this instead of using normal useEffect hooks? Now I'm confused about the right way to proceed here and I haven't found a lot of info on the internet about this.

Can you please kindly point me in the right direction? What's the appropriate way to load data and then pull it into the Recoil system? AtomFamily, selectorFamily? Am I even on the right track here?

Thanks in advance for your help,

Greg


Solution

  • I posted the question on the Recoil Github page, and got several high quality answers. Thank you very much to BenjaBobs and drarmstr for their help:

    BenjaBobs writes:

    You have a couple of options:

    fetch the array of items and store in an atom, and use a selectorFamily to access individual items in that atom. If you need to display the list somewhere, this can be a good choice since you now have an array version to iterate over

    fetch the array and iterate over them and put each into it's own atomFamily member.

    fetch only one at the time, using a selectorFamily.

    Do a combination of the first and third options and fetch a list of ids and put them into an atom and then use a selectorFamily to fetch lazily.

    EDIT: Here's a piece of state in my hobby project that uses the fourth method I mentioned https://github.com/BenjaBobs/HotsTools/blob/master/src/api/state/heroes.ts#L5-L129

    Drarmstr notes:

    Using a selectorFamily() works for read-only queries. Using an atomFamily() can allow you to load initial values, but then allow local mutation. Using atom effects can allow you to bi-directionally sync mutable data.

    Also note some ramifications based on how you choose to load your data. If you use useEffect(), or some async mechanism, to load initial values into an atomFamily(): then if the atoms are read prior to the data being loaded they will use the default atom value. If you use a selectorFamily() to fetch them, use an atomFamily() with a selectorFamily() as the default to fetch initial values, or initialize the value via a Promise in an atom effect, then the recoil state can be "pending" while you load the initial values. This can be managed automatically via React Suspense or managed explicitly by using Loadables.

    I followed up with the following question:

    @drarmstr - let's assume I want to use an atom() with a default Promise to fetch my list of items. Next, I'd want to iterate over the list items and create individual atoms - the whole point of the exercise. Should that all be done inside the same Promise?

    And dramstr replied:

    If you're using the pattern of an atom() with a Promise default to allow for locally mutable state with a default from a remote query, then the equivalent for dealing with the actual item state themselves would be to use an atomFamily() for the items and provide a default that is a selectorFamily() that uses a Promise to query the default for the individual item. e.g.:

     const itemsState = atom({
        key: 'Items',
        default: fetch(items...),
     });
     
     const itemState = atomFamiliy({   key: 'Item',   default:
     selectorFamily({
         key: 'Item/Default',
         get: id => () => fetch(id...),   
        }),
     });
    

    Big thanks to those who have responded, this has been helpful on my journey to understand Recoil.