A quick glance at Recoil

Vladimir Kopychev
The Startup
Published in
10 min readJun 27, 2020

Introduction

It’s been a while since I posted some articles about React state management libraries. You could see my tutorials using two most popular libraries I’ve worked with during the majority of my Frontend / Fullstack engineering projects — Redux and MobX. Those two libraries are very powerful and each of them has its own pros and cons and best application practices.

React constantly evolves as a technology, especially with introducing Hooks and moving to functional components and adopting most of functional programming paradigms. So state management libraries should also adopt to those changes by moving to hooks and functional programming (i.e. MobX hooks). Redux, in fact, was using functions over classes pretty much since the beginning (reducers, connect and mapStateToProps functions etc.).

In the meantime, Facebook has introduced their own state management library called Recoil that is supposed to be simple, minimalist, with much smaller boilerplate and magic compared to Redux and MobX. According to their motivation doc they paid specific attention on asynchronous state update, derived state, code splitting with distributed state and compatibility with new and upcoming React features.

The purpose of this tutorial is to take a look at a new shiny state management library and see how it looks and works compared to ones used in previous tutorials. We will use Todo app with GraphQL API backend from my old featured article. This will also allow us to check how Frontend based on React / Recoil works with relatively new GraphQL server side API.

You can check working code of the whole project in the repository.

Application description

Let’s build a simple Todo app that will consist from 2 main parts. First part is the list of users presented in the system with form to perform search and filter based on various parameters .

Second part is the actual list of todos for each user represented as a list:

Todos are displayed for a specific user i.e. `/todos/1/` and can be filtered by completed state (Completed, Not completed, All).

Server side part

For server-side part we can take a slightly modified version of the GraphQL API from React/GraphQL article.

Server-side code is located there. The list of dependencies is a bit smaller because we can drop lodash in favour of native ES6 methods

"dependencies": {
"express": "^4.17.1",
"express-graphql": "^0.9.0",
"graphql": "^15.1.0",
"nodemon": "^2.0.4"
},
"devDependencies": {
"@babel/core": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"@babel/register": "^7.10.3"
}

There are just graphql and express packages and some babel packages to properly use ES6 features (like import/export) in our Node JS application.

The entry point is index.js file which runs the server side app with babel preset to be able to recognize ES6 modules.

GraphQL schemas are mostly identical to ones from the old article, except having lodash methods replaced with vanilla JS to reduce the size of our application.

The schema has two fields — users and todos with UserType and TodoType respectively. We can filter Users list by four possible parameters defined in args. The filter itself is implemented in a resolve function (in this case just a simple lodash filter).

The same goes for the Todos list: we can potentially filter it by userId and by completed state.

To run the server side app you can go to `/server` folder and run (tested with node version 12.16.1)

npm installnode index.js

Then just open http://localhost:3001/graphql in your Chrome browser and try some queries to verify that GraphQL API works fine.

Here comes Recoil — client side application

Working code for client side app is locate there. Now let’s go through the main steps of the application building process and main recoil features.

At first, we create a skeleton react application with good old

npx create-react-app client

Then install new shiny recoil library by navigating to the client folder and running :

npm install recoil

Now, let’s take a closer look at the client application source code. RecoilRoot is one of the main concepts of recoil state management library

import { RecoilRoot } from 'recoil';<RecoilRoot>
/* your app component */
</RecoilRoot>

RecoilRoot component acts similar to provider in Redux or MobX apps, but it doesn’t define any specific store to pass. Components that need to be wired to the state should have RecoilRoot somewhere in parent components tree. Usually a good place for it is your root-level App component.

So in our tutorial all children rendered inside <App /> component should have access to the state. Now, let’s take a look at the state itself.

The core concept of Recoil state is an atom — a source of truth for the application state. You can consider atom as a part of the big shared state that can be used by components. Each atom has a unique key and a default value:

import { atom } from 'recoil';export const userListState = atom({
key: 'userListState',
default: [],
});

In the example above userListState is an empty array by default. Different data structures can be used to represent a default state.

export const userFormState = atom({
key: 'userFormState',
default: {
first_name: '',
last_name: '',
department: '',
country: '',
}
});

The atom above represents the state of the search form for filtering the list of users. Now we need to think how to implement the filter logic itself. One of the options is to send another GraphQL request and return a filtered list of users from the backend side. In order to avoid adding more load to our API and also for educational purpose (because Frontend is the main point of the tutorial) we will implement filtering on the client side.

To do this we need to look at the next core concept of the recoil state called selector. Selector represents a piece of derived state similar to the computed decorator in MobX. Selectors are quite powerful to represent computed or derived data based on multiple atoms. For example the following selector filters users list by all non-empty options represented in the filter state

import { selector } from 'recoil';import { userListState, userFormState } from './atom';export const filteredUserListState = selector({
key: 'filteredUserListState',
get: ({get}) => {
const users = get(userListState);
const filter = {...get(userFormState)};
Object.keys(filter).forEach(key => !filter[key] && delete filter[key]);
if (Object.keys(filter).length) {
return users.filter(user => {
for (let key in filter) {
if (filter[key] !== user[key]) {
return false;
}
}
return true;
});
}
return users;
},
});

Todos folder contains similar selector to filter items by completion state.

Selectors also can be asynchronous to use some promise-based logic to get derived state. It can be useful for some use cases such as getting currently logged in user etc. We may add some logic to the tutorial later to see how it works in action.

Wiring parts together

Now let’s see how it all works with react components. For this tutorial we will try to follow modern paradigm based on hooks and functional components.

As a small preparation step we create a small service library to make handling API request easier. It’s pretty much copied from my old tutorial.

Let’s start with UserList component as an example. When the component is rendered we need to load the list of users from the backend and after that we need to perform search and filter actions on the client side. Let’s take a look at the code and especially on all useful hooks presented there.

import React, { useEffect, useState } from 'react';
import { useSetRecoilState, useRecoilValue } from 'recoil';

useSetRecoilState — used to change the state or recoil atom

const setUsers = useSetRecoilState(userListState);

userRecoilValue — for just using the value without changing it

const filteredUsers = useRecoilValue(filteredUserListState);

There’s another useful hook useRecoilState used in SearchForm — it allows to both use and change state, similar to good old React useState hook

const [filterState,setFilterState] = useRecoilState(userFormState);

Now, let’s get back to our UserList component .

We need to load the list of users with an asynchronous function invoked on component mount in useEffect hook.

    useEffect(() => {
const getUsers = async () => {
const users = await apiService.getUsers();
setLoading(false);
setUsers(users);
};
getUsers();
}, [setLoading, setUsers]);

Once the list is loaded it’s stored in userListState bit of shared state. On change of that atom, the filtered selector updates to return the filteredUsers state or just a full users list if no filter options are set.

SearchForm component is used to filter results in users list. In this tutorial the form has two states — internal state, controlled by react useState hook that holds local temporary state of the form when a user changes a value:

    const [formState, setFormState] = useState({});
/* .... */
const handleChange = (event) => {
const {name, value} = event.target;
const newState = {...formState};
newState[name] = value;
setFormState(newState);
}

And shared state — on form submit we ‘sync’ the internal state of the form with our shared state atom.

const [filterState,setFilterState] = useRecoilState(userFormState);    const handleSubmit = (event) => {
event.preventDefault();
const newFilterState = {...filterState, ...formState};
setFilterState(newFilterState);
}

Please not that we keep the state immutable returning the new state object instead of modifying the existing one. This concept is very similar to how reducers work in Redux. Also official docs for Recoil tell us that the state managed by this library can be also wrapped in reducers if needed.

Once the value of the filter is changed — our selector re-calculates the value and we see a list of filtered users. Pretty much similar to the computed decorator in MobX:

Another selector for todos components follows the same logic to filter items by completion state.

Let’s try to run it!

To run the server side of the app just navigate to the `/server` folder and run the following commands:

npm install

(this will install all required packages)

node index.js

If everything is fine you should see a message that the server is running.

To run client side of the app open terminal in new tab, navigate to the `/client` folder and run

npm install

(this will install all required packages)

npm start

It will run the client app in dev mode so you can make some changes in the code and see the updated UI immediately. To see it running just open your browser and go to `http://localhost:3000/`

Some of my thoughts and conclusions

Recoil is relatively new and already quite powerful state management library. It doesn’t give full boilerplate of actions, middlewares and reducers, instead it provides only a storage for shared state (atoms) and a set of hooks to connect components to that state (similar to native useState hook).

Recoil has a concept of derived or computed state (selectors) similar to MobX computed decorator. Derived state can be synchronous or asynchronous and can also be connected to components via hooks.

Recoil probably has an easier learning curve compared to Redux (for sure) or MobX to master its main features. But please note that compared to those two well-established tools you have to implement quite a lot of logic and design patterns on your own. In this regard it looks familiar for MobX users because MobX focuses only on shared state store and actions without pushing you to use a full pre-defined architecture. Although Recoil doesn’t give you a full set of MobX features it can potentially give you the opportunity to achieve the same functionality with much smaller boilerplate if properly combined with React hooks. It doesn’t require to install a lot of additional packages or to support experimental decorator feature — you just need Recoil itself :)

As a conclusion I can say that recoil can be a great solution for early adopters in small and medium-sized apps especially if you start to design your React app in a modern way with hooks and functional components. Despite having some bugs like this one (annoyed me a lot when I worked on this tutorial) it looks very promising and actively updating by Facebook community.

So we can say it’s quite powerful but lightweight and minimalist state management tool with very interesting concept of distributed shared state split to atoms, which can be combined to get derived bits of state via selectors. Those pieces of state can be connected to react components via the wide range of Recoil hooks.

Let’s keep an eye on this project and see how it’s constantly evolving and getting stable and used in more and more real projects :)

Next topics to tackle

  • Usage of asynchronous selectors (i.e. currently authorized user)
  • State persistence (experimental feature to persist state in some storage and observe changes in stored atoms)
  • Possible design patterns and app structures with Recoil
  • Unit testing of React — Recoil apps

Thanks for reading! See you in the next posts about React.

--

--

Vladimir Kopychev
The Startup

Full Stack Engineer with passion for Frontend and UI solutions, PhD, coding at @udemy