Exploring Asynchronous Requests in Recoil
Published: 2021-09-01 12:11:30

In this post, I'll take a close look at asynchronous queries in Recoil. I'll show you what the library is capable of and how it blends seamlessly with React.

Let's get going!

## What Is Recoil?

Recoil is a state management library that maps state to React components. When the state is asynchronous, selectors behave like pure functions in the data-flow graph. The programming interface remains familiar but returns a Promise instead of a value.

The consuming components `get` what they need from selector functions that feel much like synchronous queries. This allows for the use of powerful techniques, such as loaders via React Suspense, caching, and pre-fetching requests.

## Set-up

You can find the [sample code for this article on GitHub](https://github.com/beautifulcoder/async-requests-recoil). I recommend that you clone and run it to get a better feel for this state library. I have used `json-server` to host the data of whale species. The app loads a list of available whales and allows you to select a single one to get more information. AJAX requests power the state in this app and Recoil maps async state to React components.

I will be referencing the sample code quite heavily. To fire up the app, do an `npm run json-server` and `npm start`. There is a delay of 3 seconds in the API response to illustrate some capabilities of Recoil. You can inspect the `package.json` to see the delay and a 3001 port number that hosts the data. Set a `proxy` to `http://localhost:3001`. This lets Create React App know where to fetch async data from. The code will reference endpoints via routes like `/whales/blue_whale`, without hostname or port number noise.

## React Suspense

The `<Suspense />` component _declaratively_ waits for data to load and defines a loading state. Recoil hooks into this React component when the state is asynchronous. The library fails to compile if an async request is not wrapped around Suspense. There is a workaround via `useRecoilValueLoadable`, but this needs more code to keep track of the state.

Recoil can lean on the Suspense component as following:

```javascript
<RecoilRoot>
  <Suspense fallback={<div>Loading whale types...</div>}>
    <CurrentWhaleTypes />
    <Suspense fallback={
      <div>Loading <CurrentWhaleIdValue /> info...</div>
    }> {/* nested */}
      <CurrentWhalePick />
    </Suspense>
  </Suspense>
</RecoilRoot>
```

The `fallback` declares the loader component, which can be another component with Recoil state. The loaders can be nested, so only one loader shows up at a time, and this declaratively defines how and when data loads in the app.

When you pick a single whale and `<CurrentWhalePick />` starts to load, this is the `<CurrentWhaleIdValue />` component inside the `fallback` loader:

```javascript
function CurrentWhaleIdValue() {
  const whaleId = useRecoilValue(currentWhaleIdState)

  return (
    <span>{whaleId.replace('_', ' ')}</span>
  )
}
```

The `currentWhaleIdState` is a Recoil atom that is the _source of truth_ for this query parameter. Picking a whale sets the whale id state, and this is the value that shows up in the loader. I encourage you to look at how `currentWhaleIdState` is defined because this is the dependency used by Recoil to cache requests.

The `CurrentWhalePick` component gets an async state via a query selector. Recoil has a `useRecoilValue` hook that fires the initial request and throws an exception when the component is not wrapped around `<Suspense />`.

What’s nice is that this same `useRecoilValue` hook can be used to call selectors with synchronous data. One key difference is that synchronous calls do not need to be wrapped around the Suspense component.

```javascript
function CurrentWhalePick() {
  const whale = useRecoilValue(currentWhaleQuery) // fire AJAX request

  return (
    <>
      {whale === undefined
        ? <p>Please choose a whale.</p> // zero-config
        : <>
            <h3>{whale.name}</h3>
            <p>Life span: {whale.maxLifeSpan} yrs</p>
            <p>Diet: {whale.diet} ({whale.favoriteFood})</p>
            <p>Length: {whale.maxLengthInFt} ft</p>
            <p>{whale.description}</p>
            <img alt={whale.id} src={whale.imgSrc} />
          </>
      }
    </>
  )
}
```

When the whale is `undefined`, the app is in zero-config state. After everything loads, it's good to indicate to the user what to do next, and the Recoil state makes this easy.

## Caching

Recoil selector functions are _idempotent_, meaning, a given input must return the same output value. In async requests, this becomes critically important because responses can be cached.

When selector functions `get` parameter dependencies, Recoil automatically caches the response value. The next time a component requests the same input, the selector returns a Promise that is fulfilled with a cached value.

This is how Recoil fires a request and automatically caches the response:

```javascript
const currentWhaleQuery = selector({
  key: 'CurrentWhaleQuery',
  get: ({get}) =>
    get(whaleInfoQuery(get(currentWhaleIdState)))
})
```

The query parameter `currentWhaleIdState` is an atom that returns a primitive string type. Recoil does a basic equality check when it sets the cache key lookup table. If this parameter dependency is swapped for a complex type, then the equality operator fails to recognize keys, which bursts the cache. This is because equality checks in JavaScript look for the same _instance_, which changes each time the object gets instantiated. State mutation in Recoil is immutable, and changes to a complex type sets a new instance.

One recommendation is to set parameters as primitive types to enable caching — avoid complex types unless the plan is to burst the cache every time.

If you are following along with the running app, clicking on, say, Blue Whale for the first time takes 3 seconds to load. Clicking on another whale takes a while too, but if you click back on the first pick, it loads instantly. This is the Recoil cache mechanism at work.

Remember the whale id is an atom that is the source of truth for this data. Once this one fact is set within the Recoil state, it is crucial to stay consistent and avoid introducing another parameter with the same intent.

## Pre-fetching Requests

Recoil makes it possible to fire requests as soon as an event like a click occurs. The query does not execute in the React component lifecycle until the component _re-renders_ with the new whale id parameter. This creates a tiny delay between the click event and the AJAX request. Given that async queries over the network are slow, it is beneficial to start the operation as soon as possible.

To make the query pre-fetchable, use a `selectorFamily` instead of a plain `selector`:

```javascript
const whaleInfoQuery = selectorFamily({
  key: 'WhaleInfoQuery', // diff `key` per selector
  get: whaleId => async () => {
    if (whaleId === '') return undefined

    const response = await fetch('/whales/' + whaleId)
    return await response.json()
  }
})
```

This selector function has a single `whaleId` parameter that comes from the `get` in the previous selector. This parameter has the same value as the `currentWhaleIdState` atom. The `key` in each selector is different, but the whale id parameter is the same.

This `whaleInfoQuery` selector can fire the request as soon as you click on a whale because it has the `whaleId` as a function parameter.

Next, the event handler grabs the whale id from the `e` event parameter:

```javascript
function CurrentWhaleTypes() {
  const whaleTypes = useRecoilValue(currentWhaleTypesQuery)

  return (
    <ul>
      {whaleTypes.map(whale =>
        <li key={whale.id}>
          <a
            href={"#" + whale.id}
            onClick={(e) => {
              e.preventDefault()
              changeWhale(whale.id)
            }}
          >
            {whale.name}
          </a>
        </li>
      )}
    </ul>
  )
}
```

This component renders the list of whales. It loops through the response and sets an event handler on an anchor HTML element. The `preventDefault` programmatically blocks the browser from treating the element like an actual URL link.

The `changeWhale` function grabs the whale id right off the selected element and this becomes the new parameter. A hook `useSetRecoilState` can work to mutate the whale id parameter as well, but delays the request a bit until the next render.

Because I would like to pre-fetch the request, `changeWhale` does this:

```javascript
const changeWhale = useRecoilCallback(
  ({snapshot, set}) => whaleId => {
    snapshot.getLoadable(whaleInfoQuery(whaleId)) // pre-fetch
    set(currentWhaleIdState, whaleId)
  }
)
```

Grab the `snapshot` and use `set` to mutate state immediately, and call `getLoadable` with the query and parameter to fire the request. The order between `set` and `getLoadable` does not matter because `whaleInfoQuery` already calls the query with the necessary parameter. The `set` guarantees a mutation to the whale id when the component re-renders.

To prove this pre-fetch works, set a breakpoint in `whaleInfoQuery` right as `fetch` gets called. Examine the call stack and look for `CurrentWhaleTypes` at the bottom of the stack — this executes the `onClick` event. If it happens to be `CurrentWhalePick`, the request fired at re-render and not in the click event.

Swapping the query between pre-fetch and re-render is possible via `useSetRecoilState` and `changeWhale`. The repo on GitHub has the exchangeable code commented out. I recommend playing with this: swap to re-render and take a look at the call stack. Changing back to pre-fetch calls the query from the click event.

## Sum-up

This is what the final demo app looks like:

![App state with a whale](https://blog.appsignal.com/images/blog/2021-08/async-requests-recoil.png)

In summary, Recoil has some excellent asynchronous state features. It allows for:

 - Seamless integration with React Suspense via a `<Suspense />` wrapper and a fallback component to show during load
 - Automatic data caching, assuming the selector function is idempotent
 - Async queries to fire as soon as an event like a click occurs, to help boost performance

I hope you've enjoyed this run-through of asynchronous queries in Recoil and that it's given you some inspiration to explore this exciting library!

**P.S. If you liked this post, [subscribe to our new JavaScript Sorcery list](https://blog.appsignal.com/javascript-sorcery) for a monthly deep dive into more magical JavaScript tips and tricks.**

**P.P.S. If you need an APM for your Node.js app, go and [check out the AppSignal APM for Node.js](https://www.appsignal.com/nodejs).**

*Camilo is a Software Engineer from Houston, Texas. He’s passionate about JavaScript and clean code that runs without drama. When not coding, he loves to cook and work on random home projects.*