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.
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.
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).
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.
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.
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.
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!
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:
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.
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.
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.
Understandably, the key
has to be unique to each global state variable. The default
key also represents the default value of the 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.
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 theuseRecoilState
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.
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?”
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.
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!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ 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>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDesign React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.
10 Replies to "Introducing Recoil: Simplified state management for React"
your colleague did a nice blog on using Firestore with React Hooks (https://blog.logrocket.com/react-hooks-with-firebase-firestore/). I modified it to use Recoil instead of local state so I thought I’d share here so users can have another Recoil example: https://github.com/findmory/firebase-with-react-hooks
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!
This acts similar to mobx-lite…
I’m new to react and learn a couple of months! Couldn’t understand should I only focus on Recoil or Redux!!
Have you learned Hooks API? If you haven’t, go ahead. If you had, move on to Redux, as there’s more sources.
Sure! I’m on the way of Hooks API documentation and exploring some resources from the scrimba platform.
Yes! I’m working on follow up articles!
It depends. Recoil should be easy to pick up, but for now you may be better off learning Redux first.
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.
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!