How to Manage Your React Application State With Recoil.js, Part 2/2

La Javaness IT
7 min readJan 14, 2021

In a previous article, we discussed the benefits of using Recoil compared to Redux or the Context API. We introduced basic Recoil concepts and created a first small application.

You can find the first part here.

You can find this whole tutorial in the github repository here: https://github.com/La-Javaness/recoil-pokedex/tree/local-state-part-2

In this second part, we’ll dive further into the Recoil API and cover a few topics necessary to build more complex real-life applications. We’ll learn how to:

  • Fetch data from an API
  • Manage asynchronous components with Suspense
  • Parameterise in our atoms and selectors
  • Test our atoms and selectors

Recoil Concepts Used in This Tutorial

For a refresher on basic Recoil concepts, click here.

Asynchronous selectors

A selector is an object which represents a piece of derived state. Any given selector is subscribed to a Recoil atom. Whenever that atom changes, components calling the useRecoilValue or useRecoilState hooks on that selector will receive a new value computed with the selector’s getter function.

In this article, we’ll make heavy use of asynchronous selectors. Those selectors’ getters or setters are asynchronous functions, or functions that return a Promise.

When the getter of an asynchronous selector is done running, the computed value is cached until the selector’s input atom changes value.

Suspense

React.Suspense is an experimental API that allows you to postpone the rendering of a component tree until all components in that tree are ready to render, including components that want to fetch data asynchronously before rendering. It lets you provide a fallback component until the Suspense’s children are ready to render.

Suspense aims to simplify the process of waiting for an app to be ready to load. In a Redux app, you typically maintain a isLoading flag in your store until all necessary data is fetched, and then attempt to render. With Suspense, components delegate management of the loaded state to their nearest Suspense parent, and are considered loaded only once they effectively return a DOM tree.

selectorFamily

A selectorFamily is a powerful pattern that is similar to a selector, but allows you to pass parameters to the get and set callbacks of a selector.

For instance, if you have an atom that contains a user’s data, and a user settings form, a selectorFamily can be used to destructure the atom and provide individual getters and setters for each atom property, matching the fields in the form.

An asynchronous selectorFamily memoizes the return value of the getter for each unique parameter passed to it.

atomFamily

The atomFamily is a sort of factory to make writeable atoms. It takes a parameter and returns an atom.

The default property of an atomFamily object is a function, and receives the parameter passed to atomFamily. You can use it to return a default data structure with the parameter as an id, for instance.

useRecoilCallback

The useRecoilCallback hook is similar to useCallback(), but ensures your callbacks are compatible with Recoil state. In React, useState setters don’t need to be declared as dependencies of a useCallback hook. Likewise, useRecoilState setters don’t need to be declared as dependencies of useRecoilCallback.

Another use of useRecoilCallback is that it can access a read-only snapshot of the current Recoil state, which is useful for data prefetching.

Fetch Data From An API — A Simple Pokedex

In this example, we’ll use the https://pokeapi.co/ API. It lists all Pokémons and their characteristics.

We’ll use the following endpoints:

The first one loads a paginated list of Pokémons. It replies with an array of JS objects which all contain a name property, and an API URL to fetch additional details on that Pokémon. It also provides URLs for the next and previous pages, which we’ll store as Recoil atoms.

The second one loads a given Pokémon’s details. It takes an ID as parameter and returns a JSON object with all necessary details about the Pokémon. We’ll use it later.

We create an Atom that represents our current list of Pokémons, and that updates when we navigate. It will store the return value of the first API endpoint.

In this atom, the default value is the URL to fetch the first 30 Pokémons. How convenient is it!

Let’s now create a selector that provides the list of Pokémons, and next and previous URLs, matching the above atom. The selector’s getter will perform the API call with the URL stored in the atom.

Next, let’s display the Pokémons in our application! Let’s build a List component. For the moment, we only display the name of each Pokémon.

To browse through the entire Pokédex, we create a Pagination component. When a link in that component is clicked, it updates the value of the pokemonListUrlAtom, which in turn triggers a re-render of both the List and Pagination components.

That’s it! We now know how to display the content of an API in React components using Recoil to store application state relevant to the API.

Manage Asynchronous Components With Suspense

React hooks are synchronous by design. If the useRecoilState and useRecoilValue needed to perform asynchronous operations, the initial renders of a component using them would fail. Suspense resolves this issue by delaying the rendering of components with asynchronous dependencies until they are able to resolve, displaying a fallback UI (ie. a loader) in the meantime.

Suspense is still experimental and is compatible with few technologies: React.lazy, Reach Router, and a few others. Among those, Recoil.

To use asynchronous atoms and selectors in your app, you need to use <Suspense /> or an equivalent method to track the state of your data loading before rendering components.

Since Recoil is compatible with Suspense out-of-the-box, that’s what we recommend. The Setup is simple: wrap your component with asynchronous behaviour in a <Suspense /> parent.

Asynchronous calls often involve a remote backend, which may crash, and a network connection, which may fail. Promise rejections in asynchronous functions lead to JavaScript exceptions in component renderers, which must be caught to prevent the application from crashing.

That’s why you should also wrap your component in an component (usually named <ErrorBoundary />) that will catch and display those errors before they reach the <Suspense /> component.To manage the asynchronous nature of our Pokémon list, let’s use Suspense and ErrorBoundary.

Here is our ErrorBoundary:

AsyncWrapper.js

We bundle both Suspense and ErrorBoundary in AsyncWrapper for convenience. It renders a fallback during loading, and prints any potential error, for any given component.

We can now use that AsyncWrapper to implement client-side routing. A router will be added that handles links in the Pokémon list and, for each link, loads a <PokemonDetails /> component that will be described later. We’ll also show the updated Pokémon list further down.

How To Use Params In Your Atoms And Selectors

PokemonDetails takes a Pokémon ID as a parameter (provided in our case by the react-router-dom API).We’ll use a selectorFamily that takes a Pokémon ID as a parameter and returns the corresponding selector.

When writing a selectorFamily, simply add parameters to the get and set functions. Those will be filled with the values passed when the selectorFamily is called.

Below is the code for our <PokemonDetails /> page. Note that currentId uses an atomFamily which allows you to give a parameter to your atom.

Let’s now display each Pokémon in the list using a Card that contains a Link to that Pokémon’s <PokemonDetails /> page.

Let’s now insert this component into our List, which displays up to 30 Pokémon cards. As our Card component is also asynchronous, we take advantage of the AsyncWrapper we previously wrote to manage its loading and error states.

How to test atoms and selectors ?

To dive further, let’s learn how to make our atoms and selectors testable. A robust application is a well-tested application, so Recoil code in your app should be tested.

Atom and selector testing relies on the Recoil Snapshot API. Note that it is still considered unstable, so it’s subject to change in future releases of Recoil.Below is an example of an Atom test:

Before testing selectors, we have to mock calls made to our application’s backend. We do this with the Mock Service Worker library (aka. msw).

Mocking your whole backend rather than the fetch method is recommended by Kent C. Dodds. Below is an example using msw to test a Selector:

Tools

The Recoil ecosystem is still nascent, but we already have online tools to optimise our development experience. I have personally tested these two which I found convenient and easy to use:

Conclusion

We’ve seen how to use Recoil to fetch data from an API, how to easily build components based on asynchronous Recoil fetches with Suspense, and how to manage the state of parameterised components with atom and selector families. Finally, we’ve learnt how to test Recoil code.

In this article, we only used GET APIs because we only needed to read data, but it is just as easy to set up a whole CRUD system thanks to the selectors, atoms and hooks provided by Recoil.Feel free to click the Follow button below if you don’t want to miss our next posts! You can also keep in touch with us on LinkedIn, Twitter and our website, check our GitHub, and see our other publications on Medium.

I would like to thank Steve Dodier-Lazaro and Vincent Le Badezet for proofreading this article.

About

Maxime Conan is Lead Frontend Developer at La Javaness since 2019

--

--

La Javaness IT

La Javaness brings your team and your business to the AI ​​revolution!