Recoil.js — The New, Better State Management Library for React
By: Guna Shekar Proddaturi
Tags: Recoiljs, JavaScript, Reactjs, Programming, React

Tutorial

We’ll be building a simple contacts app as mentioned above, which would first render the names of all contacts on the sidebar, and as we click on one of the names, the details of that contact will be asynchronously fetched and rendered.

Let’s cover a few basics before we move ahead.

If doing it the React way, you would ideally have a useState hook to store the currentContact and use setCurrentContact (the setter function for the state) to update the current contact when we click on another name. You can achieve similar functionality in Recoil using what is known as an “atom.”

An atom, as the name suggests, is a single unit of state capable of storing any valid form of data (objects, arrays, numbers, strings, etc.), exactly like the React state, except that you can use an atom anywhere in your application. Just as we have the useState hook in React, we have the useRecoilState in Recoil which has the same interface as the useState hook. It takes as an input an atom or a selector (we’ll see what a selector is soon) and returns an array with two values — the value itself and a setter function for this state. With next to no extra effort on your side, you can migrate from useState to useRecoilState . If you’re not getting it just yet, don’t worry — read on, complete the tutorial and all will become clear.

There may be times where you have to modify an existing state, or use an existing state to produce some other useful output. For example, in our app, we would have to use the currentContact state to fetch the details of the contact and produce a new state which contains the details of the currently selected contact. For this purpose, to get a “derived state,” we use what’s known as a “selector”.

A selector selects an existing state (which could either be an atom or another selector), “get” the value of the state, use it to perform some action (fetch related data from the server, calculate some value from the existing state, etc.) and return a modified state which we can then use. In our application, we need a selector to first fetch the list of all names (this selector does not depend on any other state) and then another selector to fetch the details of the currently selected contact (this selector depends on the currentContact state).

As shown in the graph above, since the currentContactDetails selector depends on the currentContact state. Whenever the currentContact state is updated, the currentContactDetails is re-computed — i.e., new data is fetched according to the newly set currentContact . Worried about fetching the same data repeatedly because we might select the same name? Worry no more. The “selector” wrapper provided by Recoil (we’ll see how to implement this below) acts as a “memoizer,” i.e., it caches the value corresponding to a certain input, so if you request the same contact details again, it will directly fetch it from the cache rather than fetching it from the server. Go ahead and read about Pure Functions and Memoization for better understanding.

When I talked about atoms, I said that the state can be used “anywhere in the application.” How does Recoil help us do this? It offers the RecoilRoot component, which has to be the parent component of all the components in which we want to access the state. Ideally, this would be the index.js or App.js file — i.e., your top-level component.

Finally, let’s get into the code and see how to implement all of this. Here’s the directory structure I’ve used for the project:

As you can see, we have our parent component, App , which has two children, Sidebar and Details according to our description above. This component is wrapped inside the RecoilRoot because we want all the children of App to use the Recoil state.

Sidebar component:

The sidebar component has a title and a ContactsList component renders our list of contacts. As you can see, the ContactsList component is wrapped in a Suspense . This allows us to display a fallback (loading) UI when the data for our application is being asynchronously fetched from a server.

Before we look at the ContactsList component, let’s see how we define the atoms and selectors that enable us to use state variables that are global.

recoil/atoms.js:

It’s as simple as that! Atom is nothing but a function that enables us to store state. It takes a key and a default. The key property must be unique for every atom and selector in your application — this is how the data graph knows that one “node” (or state) is different from another. The default value is exactly like what you would initially pass to the useState hook.

recoil/selectors.js:

The getContacts and getDetails functions used here simulate the action of a server — i.e., they halt for two seconds and then return the contacts and the details respectively, asynchronously.

A selector is similar to an atom, except it allows you to have a derived state instead of a fixed state (If you’re familiar with Redux, think of this as similar to the Redux thunk middleware where you can execute functions and perform operations before you update the state). Again, a selector is a function that takes an object with two (or optionally three) properties: a key , a get function (and optionally a set function if you want to modify the state). The key, as mentioned above, is a unique identifier for this selector. The get function allows you to get the value stored in the selector. As we’ll see next, we’ll use the useRecoilValue hook to get the value of a selector.

The get function of the first selector — contactsList — simply makes a query to the server to get the names of all contacts. The response that’s returned from the function is what will be available to us in our React Components.

The second selector is slightly more complicated. The currentContactDetails selector’s get function is dependent on the currentContact atom which we defined earlier. This is where we “subscribe” this selector to that atom, so that whenever the atom updates, the selector fetches a new value from the server without our intervention.

The selector’s get function, receives an object in its parameter which has a property which is also named get and this parameter guy get is a function that you can use to fetch the values of other atoms and selectors in the app. So we’ll use this to fetch the value of the currentContact atom and fetch the appropriate details. If you use the get function to fetch any recoil state, your selector automatically subscribes to that recoil state and will re-compute whenever any of its dependent-states change.

Now, how do we use these atoms and selectors in our ContactsList component?

ContactsList component:

In the first few lines, we’re importing the useRecoilState and useRecoilValue hooks along with the atoms and selectors we just defined. In the component, we’re first initialising the atom in the same way we would initialise a React state using the useState hook. The useRecoilState hook takes an atom as an input and returns an array that has the state itself and the setter function to modify the state.

As mentioned above, to read the value of the selector, we use the useRecoilValue hook (you can very well use the useRecoilState for a selector as well). The component returns the list of all contacts present in the contacts array. The classNames are just to differentiate between the name currently selected and those which are not. To know which is selected, we use the atom’s value — i.e., the currentContact state — and update this currentContact to the new name when we click on it.

But there’s one issue: There is no default value as such for the selector, and, as I said, it takes two seconds for our getContacts() function to fetch data. What happens in the two seconds it takes for the data to be fetched?

Enter React Suspense .

As I already mentioned, we wrapped the ContactsList component in Suspense , so any unresolved promise (if you’re not aware of what this means, think of it like data that is currently being fetched) will lead to the rendering of our fallback UI — i.e., “Loading Contacts…”.

Details component:

Similar to the Sidebar component, the Details component renders a card that will display the details of the selected contact.

Card component:

As you can see, we get the details of the current contact from the currentContactDetails selector that we defined earlier.