Ohans Emmanuel Visit me at www.ohansemmanuel.com to learn more about what I do!

Introducing Recoil: Simplified state management for React

5 min read 1594

Getting Started With Recoil

Recoil seems to be the new kid on the state management block — a block that’s considered overcrowded by many. Redux, MobX, Context… the list goes on and on.

Before going on to discuss Recoil, though, I’d like to point out that this new state management library is not the “official” state management library for React.

While Recoil may have been built (and released) by engineers from Facebook’s teams, it does not represent an official library for state management. However, just as Redux isn’t an official state management library for React, Recoil may very well be adopted by many if it proves valuable to the community and React ecosystem at large.

The main problem Recoil solves

While it provides its own twist, the problem Recoil solves is the same as most other state management libraries: global state management.

I don’t have years of experience with Recoil — heck, no one does! However, after its release, I was curious to try my hand at it. I read the docs, every single page, and I toyed with it on a small project. Here are the areas where I think Recoil shines.

1. It feels just like React

Not many state management libraries can boast of working in the same mental model as React. Redux and MobX, for example, introduce their own specific terminologies and principles.

However, Recoil feels like using a global version of React’s useState. It also supports Concurrent Mode, which is a huge plus (this is still in the works at the time of writing).

2. There’s very little to learn

I love Redux. I also think modern Redux doesn’t have all the boilerplate we once hated it for. However, with Redux, you still need to learn a bit more compared to Recoil. With Recoil, there’s really not all that much to learn. That in itself is powerful.

3. Simplicity

I have no doubt that whatever app you build with Recoil you could build just as well with Redux or MobX (or the many other libraries out there). However, Recoil’s simplicity is hard to beat.

4. App-wide observation

Similar to its counterparts, Recoil handles app-wide state observations well. However, the tooling is nowhere near what a more mature library like Redux offers at the moment. It will undoubtedly take some time to catch up.

Recoil core concepts

Recoil's Main Concepts: Atoms And Selectors

The two most important concepts of Recoil are atoms and selectors. These are fancy terms for pieces of state and computed values based on state.

Don’t worry if you don’t get this now; I explain further in the next section. However, what’s important to note is how we’ve stripped the library to just two core principles — talk about simplicity!



Writing your first Recoil app

Let’s build the most trivial Recoil app possible: a simple counter.

Without Recoil, here’s what a simple counter looks like:

import React, { useState } from "react";

export const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(c => c + 1)}>Increase count</button>
    </div>
  );
}

We have local state implemented via useState and a button that calls the state updater setCount to increase the state count variable.

There are two things done here:

  1. Declaring a new state variable
  2. Updating the state variable.

Let’s have these done with Recoil. The only difference is having the state global to the entire application as opposed to a single component. Below are the steps to take.

1. Wrap the root component in RecoilRoot

The first step to make this basic example work with Recoil is to wrap your app root component in RecoilRoot.

// before now, Counter would be rendered as follows
ReactDOM.render( <Counter />, document.getElementById("root"))

// now 
import { RecoilRoot } from "recoil";

ReactDOM.render( 
 <RecoilRoot>
    <Counter />
 </RecoilRoot>, document.getElementById("root"))

RecoilRoot acts as a global context provider for the entire tree of components in which you want to use shared global state. For more advanced readers, this is very obvious if you take look at the return statement from Recoil’s internals.

2. Create the global state value

The process of creating global state values is simple. Recoil calls these atoms.

Assume you’ve got a state object like this:

{
    key1: complexStateDataStructure, 
    key2: [id1, id2, id3],
    key3: complexStateDataStructure
}

In this example, every state object key could be referred to as an atom. Atoms represent a piece of state, i.e., key1, key2, and key3.

Let’s create an atom to hold the count state variable.

// Note the import 👇
import { atom } from "recoil";

// creating the state value
const count = atom({
  key: "count",
  default: 0
});

First we import atom from recoil, then invoke this method with a key and default value.


More great articles from LogRocket:


Understandably, the key has to be unique to each global state variable. The default key also represents the default value of the variable.

3. Consume the global state variable

Having created the state variable, we may now retrieve the value as follows:

// Note the import below
import { useRecoilState } from 'recoil' 

export const Counter = () => {
  // before 👇 
  //  const [count, setCount] = useState(0);
  // now 👇
   const [countState, setCount] = useRecoilState(count);;
  ...
}

The major changes are seen above. Instead of using useState, you should now use the Recoil Hook useRecoilState to retrieve a global state value.

Note that the value passed to the useRecoilState refers to the return value of creating an atom.

import { atom } from "recoil";

// count here is passed to useRecoilState
const count = atom({
  key: "count",
  default: 0
});

This is important. useRecoilState must be passed a RecoilState object. This is the result of invoking the atom function, similar to how you’d pass a context object to the useContext Hook.

Put all these together and you have your first Recoil app.

Our Simple Counter App WIth Recoil

This is far from impressive, but it explains the very basics of Recoil.

So let’s do more.

N.B.: The global state variable, count, may now be used in different components within the app tree by invoking the useRecoilState Hook.

So, do you still remember the core concepts of Recoil?

While there are obviously more advanced use cases for Recoil than building a counter, at its core lies simplicity. As stated earlier, there are two core concepts: atoms and selectors. You’ve been introduced to the first, now here’s how the second works.

Selectors

If you’ve worked with older state management libraries like Redux or MobX, the concept of selectors shouldn’t be new to you. As a refresher, selectors serve as an efficient way to get computed values from your application state.

In other words, if a value can be computed from state, don’t re-initialize it at a separate state key; use selectors. Check out the CodeSandbox below.

In this example we have the text Is even count displayed below the counter. For this to work correctly, we need to know if the state count value is even or not. Instead of initializing another state value, we can compute this from the previous count value.

Here’s how:

import {selector} from 'recoil'

const isEvenCount = selector({
  key: "evenCount",
  get: ({ get }) => {
    const state = get(count);
    return state % 2 === 0;
  }
});

This looks confusing at first, but it’s quite simple. Let me walk you through it.

In the code block above, I compute a new value called isEvenCount. This is the result of invoking the selector function from recoil. The selector is invoked with an object. As with atoms, the object also has a key property. The key has to be a unique string; it shouldn’t be the same as any previous atom/selector keys.

The fun bit is the get property. This is a method invoked with an internal get function from recoil. With this get function, you may retrieve any state value, as seen in the line:

const state = get(count);

In the return statement, we then compute the isEvenCount value as get(count) % 2 == 0, which basically says, “Is the count state variable divisible by 2?”

Using selector values

Having computed the isEvenCount value, using it within a component is similar to how you’d use an atom’s return value but with a different Hook, as seen below:

export const Counter = () => {
   const [countState, setCount] = useRecoilState(count);
   // see this 👇
   const value = useRecoilValue(isEvenCount);

  ...
}

For more context, here’s the full code:

import {selector, useRecoilState} from 'recoil' 

const isEvenCount = selector({
  key: "evenCount",
  get: ({ get }) => {
    const state = get(count);
    return state % 2 === 0;
  }
});

export const Counter = () => {
   const [countState, setCount] = useRecoilState(count);
   // see this 👇
   const value = useRecoilValue(isEvenCount);

  ...
}

To get the value within your component, you use the useRecoilValue Hook, as shown above.

It is worth mentioning that the return value from invoking the selector is an object expected to be passed to the Hook useRecoilValue. Only then do you get the actual value you seek.

Apart from just getting the value, this also creates a subscription to state, i.e., a new value is recomputed every time the state value the selector depends on changes. In our example, every time count changes, a new value for isEvenCount is recomputed.

Conclusion and what’s next

Though very young, Recoil is looking promising! As a getting-started guide, I’ve only scratched the surface of what’s possible.

Watch this space for more Recoil related articles. I’ll be doing some more writing and research in that regard. Also, now would be a good time for you to watch the official release video. If you’re looking to take a deep dive into Recoil, I suggest you take some time to watch it.

Cheers!

Get setup with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Ohans Emmanuel Visit me at www.ohansemmanuel.com to learn more about what I do!

10 Replies to “Introducing Recoil: Simplified state management for React”

  1. Nice job mate. Can you add a post for the real world? fetching api -> set the response data to global state store -> render the result etc. E2E!

  2. It depends. Recoil should be easy to pick up, but for now you may be better off learning Redux first.

  3. I started looking into redux, but storing only a single variable was so complex that I thought it was not worth it and probably creates more headbreaking bugs than it solves.

  4. Wow, have you looked at recoil-outside?: https://www.npmjs.com/package/recoil-outside It let’s you use state variables in regular javascript functions. This tool came out just 2 months ago and is a lifesaver! I just started with react. As a beginner (comming from C/C++ programming), not being able to use state-variables in an easy way was frightening as hell! Now I can get back in my comfort-zone. I don’t know what hardcore evangelists or functional programmers will say about this new development (or if it is stable for critical software), but I love it!

Leave a Reply