One of the most essential parts of any app is state management. It dictates what users see, how the app looks, what data is saved, and so on. Hence, it's no surprise that there are so many open-source tools dedicated to making state management easier and more user-friendly.

Of the multitude of JavaScript UI frameworks, React is perhaps the one that enjoys the most dynamic ecosystem, including state management libraries. This article will cover two of those libraries - Recoil vs Redux - and show how both of them differ from one another, and their different approaches to application development.

While Redux is considered the most popular state management library, Recoil is Facebook's experimental React state management framework that has lately received a lot of attention. Let’s take a look at what Recoil vs Redux are, their performance, and whether it’s a good idea to use one over the other.


Table of contents

What is a state management library?
    ➤  Why do you need a state management library?
What is Recoil js?
What is Redux?
    ➤  How does Redux work?
Recoil vs Redux
    ➤  Terminology
    ➤  API
    ➤  Developer experience
    ➤  User experience
Conclusion


What is a state management library?

A state management library gives you a convenient way to model the state of your application, derive computed values from it, and monitor it for changes.

Simply put, managing your data in frontend frameworks is equally important as databases. Having a state management system that manages your data well, helps you avoid a number of API calls.

Why do you need a state management library?

Without state management, data is spread everywhere, as you can see in the below image. You can't actually have a single source of truth for your data because this type of structure is tough to maintain and slows down your development process.

Without state management: Data is everywhere

When you have a state management system in place, data flows in one direction, from your app to the state and vice versa. You are aware of the actual location of your data. These state management technologies also provide a snapshot of the full data set at a certain moment in time. This way, you know exactly where your data is, which speeds up development.

All in all, managing your data well makes your application more efficient.

With data management: Data flows in one direction

What is Recoil js?

Recoil js is a new state management library for React. It’s “minimal and Reactish” like the website says and, in practice, it looks a lot like using the normal use state hooks but with a couple of nicer little touches that make it easier to understand how the data is flowing through your application.

What is Redux?

Redux is an open-source library in the JavaScript world, and very popular in the domain of React and React Native. It is kind of a compulsory go-to tool for every React developer, hence understanding the concepts of React and Redux go hand-in-hand.

It also implements Flux, the proposed “architecture” to handle state on a React application.

How does Redux work?

Redux provides a centralized store for your application state and logic. It gives components direct access to the data they need, being a predictable state container designed to help you write JavaScript apps that behave consistently across client-server and native environments.

Redux enables you to make building complex applications easier by providing consistency in the data across the application. As the application grows, managing the data with the component state becomes challenging. But in order to use Redux, you don’t need to create a React app - you can use Redux with any UI framework or library for managing the app state. Redux has a large ecosystem of add-ons to fit your needs.

What’s more, you can trace when, where, why, and how your application state changes, by using Redux Devtools.

Recoil vs Redux

Redux has been the library of choice for a long time, but with the launch of Recoil, we now also have a React-specific library that easily blends with React’s latest features.

Let’s take a look what when does it make sense to use Recoil and Redux, their state approaches, performance, API differences, and both developer and user experience.

Terminology

Redux

Unlike Recoil, Redux employs many more terms:

actions - it sends a message to Redux and can contain data; an action is an event that defines something that happened in the application; it has a type field on a JavaScript object.

reducers - an event listener that handles events based on the received action; receives the current state and an actionobject; it can update the state if need be ((state, action) => (newState)).

store - it is where the Redux application state lives; some planning is required to "design" the store in order to ensure that updates are performed on the smallest set of data, because updates to the store will trigger updates in the UI.

A fairly common scenario is accidentally sending an update to the store with a set of values, in which only one of the values differs from the ones found in the store. They will order components to re-render for all the received, but only one value was really updated. Nevertheless, we spend computing resources to tell components to update which was not necessary due to the poor design of the store/reducer/action.

dispatch - allows you to update the state by calling store.dispatch(); it dispatches actions, i.e., they trigger an event.

selectors- extracts specific pieces of information from a store; it avoids repeating logic.

Let's take a look at the example below:

Just definitions to map actions with reducer’s operations.
constants.js

export const LOGIN_SUCCESSFUL = 'USER_DATA_LOGIN_SUCCESSFUL';
export const LOGOUT_SUCCESSFUL = 'USER_DATA_LOGOUT_SUCCESSFUL';

Defines how the state of the app changes according to the received actions. This example has two slices, one for userData and another one for the shoppingCart. The action for successful login will record the user’s data into the state. On successful logout, we clear the user’s data and shopping cart.

reducers.js

import { LOGIN_SUCCESSFUL, LOGOUT_SUCCESSFUL } from './constants';

const initialState = {
  userData: {},
  shoppingCart: {},
};

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case LOGIN_SUCCESSFUL:
      return {
        ...state,
        userData: {
          ...action.payload.userData,
        },
      };
    case LOGOUT_SUCCESSFUL:
      return {
        ...initialState,
      };
    default:
      return state;
  }
};

Definition of the actions which will trigger state changes. They can receive a payload to introduce new data into the state. We have an action to be called after a successful login and another one for the successful logout.

actions.js

import { LOGIN_SUCCESSFUL, LOGOUT_SUCCESSFUL } from './constants';

export const postSuccessfulLogin = userData => ({
  type: LOGIN_SUCCESSFUL,
  payload: {
    userData,
  },
});

export const postSuccessfulLogout = () => ({
  type: LOGOUT_SUCCESSFUL,
});

How to extract data from the state. Here we append the user’s first and last name, extracted from the userData slice of the store.

selectors.js

export const userFullNameSelector = state =>
  `${state.userData.firstName} ${state.userData.lastName}`;

Recoil

Only has a few concepts that are straightforward:

atom - a piece of state; each atom is like a mini-store on itself and it becomes harder to fall into the previous scenario of Redux since atoms are completely decoupled and aim to hold data that will be updated in one go.

selector - the base where a piece of state is calculated.

Example below:

state.js

import { atom, selector } from 'recoil';

export const userData = atom({
  key: 'user-data',
  default: {},
});

export const userFullName = selector({
  key: 'user-full-name-selector',
  get: ({ get }) => {
    const user = get(userData);
    return `${user?.firstName} ${user?.lastName}`.trim();
  },
});

component.js

import User from 'components/User';
import React, { useCallback } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { userData, userFullName } from 'state';

export const Component = () => {
  const [user, setUser] = useRecoilState(userData);
  const fullName = useRecoilValue(userFullName);

  const handleUser = useCallback(
    (userInfo) => {
      setUser(userInfo);
    },
    [setUser]
  );

  return (
    <>
      <h2>{`Welcome ${fullName}`}</h2>
      <User user={user} setUser={handleUser} />
    </>
  );
};

API

Redux

  • Allows you to wrap the entire app in a provider component;
  • Lets you build a store using createSlice() alongside configureStore(), which comes with actions and reducers;
  • Requires the useDispatch and useSelector hooks to dispatch actions and make use of selectors.

Recoil

  • Allows you to wrap the entire app in a provider component;
  • Lets you start a store using atom(), which also comes with a selector and dispatchable actions built-in.

Developer experience

Redux

Redux is good at enforcing developer best practices, providing you with a simple pattern to follow so you don't have to make too many implementation decisions as you build out features. It also allows you to abstract your API requests into actions so everything is held in one single place.

Recoil

Recoil has been said to be quite easy to use. It allows you, for instance, to make asynchronous requests in selectors to get started with the state from an API request.

User experience

Redux

If you wish to manage loading and error states, you might need custom implementation. It is good practice to keep track of a global isLoading variable in the reducer.

Recoil

Since Recoil integrates with React’s latest mode, it enables you to handle loading states with Suspense and errors with an ErrorBoundary, allowing you to build your application in a more declarative way.

Async Selector version

state.js

import api from 'api';
import { atom, selector } from 'recoil';

export const userDataAsyncSelector = selector({
  key: 'user-data-async-selector',
  get: async () => {
    const { user } = await api.requestAuthenticatedUser();
    return user;
  },
});

export const userDataAsync = atom({
  key: 'user-data-async',
  default: userDataAsyncSelector,
});

component.js

// https://reactjs.org/docs/error-boundaries.html
import ErrorBoundary from 'components/ErrorBoundary';
import Loader from 'components/Loader';
import User from 'components/User';
import React, { Suspense, useCallback } from 'react';
import { useRecoilState } from 'recoil';
import { userDataAsync } from 'state';

const ComponentAsync = () => {
  const [user, setUser] = useRecoilState(userDataAsync);

  const handleUser = useCallback(
    (userInfo) => {
      setUser(userInfo);
    },
    [setUser]
  );

  return <User user={user} setUser={handleUser} />;
};

export const Component = () => (
  <Suspense fallback={<Loader />}>
    <ErrorBoundary>
      <ComponentAsync />
    </ErrorBoundary>
  </Suspense>
);

Conclusion

Redux has been established as the leader in global state management for a while, and it’s certainly not going anywhere soon. However, Recoil is becoming a serious contender for blending in with React.

A great perk for using Recoil is its integration with React's Suspense, which allows a straightforward way of handling asynchronous tasks and detecting their state, hence enabling you to quickly set up loading screens without requiring that much code of logic to check that the application is loading data from the server.

Even though Redux is the best option for bigger projects and the best for a production application, it’s fair to say that Recoil may be a better pick when it comes to both developer experience and user experience. Recoil is still in an experimental stage, but it may provide performance advantages if your app is complex enough.

More than 500.000 people read our blog every year and we are ranked at the top of Google for topics such as React, Recoil and Redux. If you liked this blog post and would love to read all our blog posts, take a look here.


Read also:


New call-to-action