linkedin Skip to Main Content
Categories

Redux vs Recoil – Comparing Two React State Libraries

Development

State management is indispensable when building medium to large-scale React projects due to the large number of components that must be able to communicate with each other efficiently. Also, these large projects may have logic implemented on the front end that requires a global application state.

Luckily, in addition to the state management solutions and features shipped with React, there are specialized libraries built for managing state in React applications.

We’ll take look at two popular options out of the many libraries out there—Redux and Recoil—and show you how to implement them.

What is a state management library?

A state management library provides a structure and functionalities for modeling and accessing the global application state. Data is centralized in one location, usually a “store” where every component can access that data, set values, and monitor for changes.

Without state management, components will be unable to get data contained in other components without the use of something like “prop drilling”.

Diagram of application components without state management

With state management, however, every component can have access to and write to the same application state.

Diagram of application components with state management

Next, let’s look at how we can use state management libraries like Redux and Recoil to manage state in our React applications.

Redux

According to the Redux docs, “Redux is a pattern and library for managing and updating application state, using events called ‘actions’.” It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.

Here are a few things the Redux documentation wants us to know before we proceed.

Why use Redux?

Redux manages the “global” application state – a state that is accessible across many parts of your application.

The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.

Redux is more useful when:

  • The app state is needed in numerous locations throughout the app and is regularly updated over time.
  • The logic to update that state may be sophisticated.
  • The app has a medium-to-large scaled codebase and might be worked on by many people.

Not all applications require Redux. Spend some time considering the type of app you’re creating and deciding which tools would be most helpful in resolving the issues you’re working on.

Now that that’s out of the way, let’s set up Redux in a React application.

Redux components and terminology

Redux Store, Action, and Reducer are the three main parts of Redux.

Redux Store is the “brain” of our state management structure. 

It manages the state and dispatches Actions

Dispatch is a method in the Redux store. The state can only be updated by calling store.dispatch() and providing an action object.

Action refers to the commands passed to the Reducers, which can be interpreted by it. It stores information regarding the user’s actions.  There is a type and a payload in the action. The payload contains the actual data provided by the action, whereas the type is only a string with the action name.

Reducers are functions that read the instructions sent by the Action and update the store using the state. Reducers specify how the application’s state will change in response to the actions sent to the store.

We will use a counter application as a case study as we move on, to discuss how to set up and use Redux in an app.

Install and setup Redux

Add Redux Toolkit and React Redux packages to your project:

npm install @reduxjs/toolkit react-redux
Code language: CSS (css)

Create a Redux Store

Create a new file – ./src/store/index.js. 

// ./src/store/index.js import { configureStore } from '@reduxjs/toolkit' export default configureStore({   reducer: {}, })
Code language: JavaScript (javascript)

Here, we import the configureStore API from Redux Toolkit and create an empty Redux store, and export it.

This creates a Redux store with an empty reducer function. Next, we’ll have to make the store accessible for the rest of our React application.

Provide Redux store to the application

We can make the newly created store available to our React components by importing and wrapping the React Redux <Provider> over the application. We’ll do all that in the ./src/index.js file.

./src/index.js import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; // import Provider import { Provider } from "react-redux"; // import store import store from "./store/index"; import App from "./App"; const rootElement = document.getElementById("root"); const root = createRoot(rootElement); root.render(   // wrap application with Provider   <Provider store={store}>     <StrictMode>       <App />     </StrictMode>   </Provider> );
Code language: JavaScript (javascript)

Create Redux state slice(s)

We’ll create redux state slices for two pieces of state: booksSlice and savedBooksSlice

Creating a slice requires a string name to identify the slice, an initial state value, and one or more reducer functions to define how the state can be updated. Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice.

First, create a slice for our books array state in a new file: ./src/store/books/booksSlice.js:

// ./src/store/books/booksSlice.js import { createSlice } from "@reduxjs/toolkit"; export const booksSlice = createSlice({   name: "books",   initialState: {     value: [       {         id: "001",         title: "Harry Potter and the Deathly Hallows",         price: 5.0,         rating: "5.0"       },       {         id: "002",         title: "Harry Potter and the Goblet of Fire",         price: 5.0,         rating: "4.8"       }     ]   },   reducers: {     set: (state, action) => {       return action.payload;     }   } }); // Action creators are generated for each case reducer function export const { set } = booksSlice.actions; export default booksSlice.reducer;
Code language: JavaScript (javascript)

Here, we import the createSlice API from Redux Toolkit and export booksSlice, and define the name, initialState, and reducers using createSlice.

Next, we’ll create a state slice for savedBooks. Create a new file: ./src/store/books/booksSlice.js:

// ./src/store/books/booksSlice.js import { createSlice } from "@reduxjs/toolkit"; export const savedBooksSlice = createSlice({   name: "savedBooks",   initialState: [],   reducers: {     add: (state, action) => {       // get the payload object from by destructuring the action parameter       //  assign value of payload to book       const { payload: book } = action;       return [...state, book];     },     remove: (state, action) => {       // get the payload object from by destructuring the action parameter       //  assign value of payload to bok       const { payload: book } = action;       const bookIndex = state.findIndex((x) => x.title === book.title);       // if no match, return the previous state       if (bookIndex < 0) return state;       // avoid mutating the original state, create a copy       const stateUpdate = [...state];       // then splice it out from the array       stateUpdate.splice(bookIndex, 1);       return stateUpdate;     }   } }); // Action creators are generated for each case reducer function export const { add, remove } = savedBooksSlice.actions; export default savedBooksSlice.reducer;
Code language: JavaScript (javascript)

Next, we’ll make these state slices global by importing them into our store.

Add slice reducers to the store

Now, we need to import the reducer functions from the state slices and add it to our store. Back in ./src/store/index.js:

// ./src/store/index.js import { configureStore } from "@reduxjs/toolkit"; import savedBooksReducer from "./books/savedBooksSlice"; import booksReducer from "./books/booksSlice"; export default configureStore({   reducer: {     books: booksReducer,     savedBooks: savedBooksReducer   } });
Code language: JavaScript (javascript)

Now that we’ve configured our store, let’s use our state in our components

Use Redux state and actions in React components

Now we can use the React Redux hooks to let React components interact with the Redux store. We can read the data from the store with useSelector, and dispatch actions using useDispatch

First, in ./src/SavedBooks.js component, we’ll use useSelector to read the savedBooks data.

import React from "react"; import { useSelector } from "react-redux"; export function SavedBooks() {   const savedBooks = useSelector((state) => state.savedBooks);   // function to get total price from savedbooks array   const getTotalPrice = (savedBooks) => {     const totalPrice = savedBooks.reduce(       (totalCost, item) => totalCost + item.price,       0     );     return totalPrice;   };   return (     <header>       <p> No. of Books: {savedBooks.length} </p>       <p> Total price: ${getTotalPrice(savedBooks)} </p>     </header>   ); }
Code language: JavaScript (javascript)

Next, we’ll use useSelector and useDispatch to read and update the books and savedBooks state respectively in the ./src/Books.js component.

To dispatch the actions add and remove we need to import the actions and use the useDispatch hook provided by react-redux to dispatch our actions.

// ./src/Books.js import React from "react"; import { useSelector, useDispatch } from "react-redux"; import { add, remove } from "./store/books/savedBooksSlice"; export function Books() {   const dispatch = useDispatch();   const books = useSelector((state) => state.books.value);   return (     <div>       <ul className="Books">         {books.map((book) => {           return (             <li className="book" key={book.title}>               <header>                 <h3> {book.title} </h3>                 <p>Rating: {book.rating} </p>                 <p> ${book.price} </p>               </header>               <button onClick={() => dispatch(add(book))}> Add </button>               <button onClick={() => dispatch(remove(book))}> Remove </button>             </li>           );         })}       </ul>     </div>   ); }
Code language: JavaScript (javascript)

Great! Now, our app works with Redux!

Next, we’ll explore another interesting state management library – Recoil

Recoil

Recoil, according to its docs, is a newer state management library built by the Facebook open-source community. It’s best explained by looking at the core concepts from the docs, which we recap below.

Recoil components and terminology

Recoil lets you create a data-flow graph that flows from atoms (shared state) through selectors (pure functions) and down into your React components. Atoms are units of state that components can subscribe to. Selectors transform this state either synchronously or asynchronously.

Atoms are units of state. They’re updatable and subscribable: when an atom is updated, each subscribed component is re-rendered with the new value.

A selector is a pure function that accepts atoms or other selectors as input. When these upstream atoms or selectors are updated, the selector function will be re-evaluated. Components can subscribe to selectors just like atoms, and will then be re-rendered when the selectors change.

You can find out more from the documentation.

Let’s move on and see how we can use Recoil in our React application.

Install and set up Recoil

To install the latest stable version, run the following command:

npm install recoil #or yarn add recoil
Code language: CSS (css)

Now that we have Recoil installed, we have to add RecoilRoot to our app’s root component in ./src/App.js:

// ./src/App.js import { Books } from "./Books"; import { SavedBooks } from "./SavedBooks"; // import RecoilRoot import { RecoilRoot } from "recoil"; export default function App() {   return (     <RecoilRoot>       <div className="App">         <SavedBooks />         <Books />       </div>     </RecoilRoot>   ); }
Code language: JavaScript (javascript)

Next, we’ll create our atoms which will contain our books and savedBooks state.

Create books state atom

Create a new file ./src/atoms/books.js:

// ./src/atoms/books.js import { atom } from "recoil"; const booksState = atom({   key: "books",   default: [     {       id: "001",       title: "Harry Potter and the Deathly Hallows",       price: 5.0,       rating: "5.0"     },     {       id: "002",       title: "Harry Potter and the Goblet of Fire",       price: 5.0,       rating: "4.8"     }   ] }); export { booksState };
Code language: JavaScript (javascript)

Create savedBooks atom state

Create another file ./src/atoms/savedBooks.js:

// ./src/atoms/savedBooks.js import { atom } from "recoil"; const savedBooksState = atom({   key: "savedBooks",   default: [] }); export { savedBooksState };
Code language: JavaScript (javascript)

In order to read from and write to an atom, Components should use useRecoilState(). This means, to use the books atom state in the Books component, we simply import useRecoilState() and booksState.

To read from an atom, we use useRecoilValue:

// ./src/Books.js import React from "react"; import { useRecoilValue } from "recoil"; import { booksState } from "./atoms/books"; export function Books() {   const books = useRecoilValue(booksState);   return (     <div style={{ border: "1px solid" }}>       <ul className="Books">         {books.map((book) => {           return (             <li className="book" key={book.title}>               <header>                 <h3> {book.title} </h3>                 <p>Rating: {book.rating} </p>                 <p> ${book.price} </p>               </header>             </li>           );         })}       </ul>     </div>   ); }
Code language: JavaScript (javascript)

Also, we can do the same thing in our savedBooks component – ./src/SavedBooks.js:

// ./src/SavedBooks.js import React from "react"; import { useRecoilValue } from "recoil"; import { savedBooksState } from "./atoms/savedBooks"; export function SavedBooks() {   const savedBooks = useRecoilValue(savedBooksState);   // function to get total price from savedbooks array   const getTotalPrice = (savedBooks) => {     const totalPrice = savedBooks.reduce(       (totalCost, item) => totalCost + item.price,       0     );     return totalPrice;   };   return (     <header style={{ border: "1px solid", marginBottom: "6px" }}>       <p> No. of Books: {savedBooks.length} </p>       <p> Total price: ${getTotalPrice(savedBooks)} </p>     </header>   ); }
Code language: JavaScript (javascript)

Now that we know how to read our state, how do we modify it?

To do that we have to do a few things. Let’s bring the add and remove functions into our Books component:

// .src/Books.js import React from "react"; import { useRecoilValue, useSetRecoilState } from "recoil"; import { booksState } from "./atoms/books"; import { savedBooksState } from "./atoms/savedBooks"; export function Books() {   const books = useRecoilValue(booksState);   const setSavedBooks = useSetRecoilState(savedBooksState);   const add = (book) => {     //  assign value of payload to bok     setSavedBooks((state) => {       return [...state, book];     });   };   const remove = (book) => {     //  assign value of payload to bok     setSavedBooks((state) => {       const bookIndex = state.findIndex((x) => x.title === book.title);       // if no match, return the previous state       if (bookIndex < 0) return state;       // avoid mutating the original state, create a copy       const stateUpdate = [...state];       // then splice it out from the array       stateUpdate.splice(bookIndex, 1);       return stateUpdate;     });   };   return (     <div style={{ border: "1px solid" }}>       <ul className="Books">         {books.map((book) => {           return (             <li className="book" key={book.title}>               <header>                 <h3> {book.title} </h3>                 <p>Rating: {book.rating} </p>                 <p> ${book.price} </p>               </header>               <button onClick={() => add(book)}> Add </button>               <button onClick={() => remove(book)}> Remove </button>             </li>           );         })}       </ul>     </div>   ); }
Code language: JavaScript (javascript)

In the code above, you can see that we assigned const setSavedBooks = useSetRecoilState(savedBooksState).

With this, we can modify our savedBooksState atom which we did by using setSavedBooks within our add and remove functions.

Using selectors

Remember our getTotalPrice function which dynamically gets the total price of saved books? We’ll use a selector to dynamically render the total price of the items in the savedBooks array.

First, we have to create a new file ./src/selectors/totalPrice.js:

// ./src/selectors/totalPrice.js import { selector } from "recoil"; import { savedBooksState } from "../atoms/savedBooks"; const totalPrice = selector({   key: "getTotalPrice",   get: ({ get }) => {     const savedBooks = get(savedBooksState);     const totalPrice = savedBooks.reduce(       (totalCost, item) => totalCost + item.price,       0     );     return totalPrice;   } }); export { totalPrice };
Code language: JavaScript (javascript)

The totalPrice selector keeps track of savedBooksState and re-runs if there are any changes.

Back in the SavedBooks component, we import our selector and use it.

// ./src/SavedBooks.js import React from "react"; import { useRecoilValue } from "recoil"; import { savedBooksState } from "./atoms/savedBooks"; import { totalPrice } from "./selectors/totalPrice"; export function SavedBooks() {   const savedBooks = useRecoilValue(savedBooksState);   const getTotalPrice = useRecoilValue(totalPrice);   return (     <header>       <p> No. of Books: {savedBooks.length} </p>       <p> Total price: ${getTotalPrice} </p>     </header>   ); }
Code language: JavaScript (javascript)

There we go! We’ve successfully used Recoil for state management in our React application.

Other state libraries to consider

Between this article and Global State Management in React without a Library, you’ve learned how to handle state in React apps using a variety of methods and tools, including Redux and Recoil for medium to big projects and Hooks and Context API for small to medium projects.

Nevertheless, the React ecosystem provides a wide range of state libraries. The majority of them were developed and constructed with a particular idea and use case in mind, so if you want to use any of them, you’ll probably have a good reason for doing so.

A few of them include:

  • Zustand – a state-management solution that is compact, quick, and scalable. It is particularly concerned with module state. This library is independent and is based on Hooks.
  • Jotai – a simple and extensible React state management library that offers a simple API. It is designed for computed values and asynchronous actions to state, and it supports TypeScript.
  • Mobx –  an independent stage-management library that isn’t platform-specific, thus it can be used outside of React. It applies functional reactive programming in a transparent manner (TFRP). Different Stores are supported by MobX, unlike certain libraries like Redux. MobX has a relatively low learning curve, so getting started with it is simple. 

My name is Victory Tuduo and I am a software developer who loves building web applications, creating flexible UI designs, mentoring, and writing technical articles. You can find out more about me and my work here and here.