Open In App

State Management in React: Context API vs. Redux vs. Recoil

Last Updated : 22 Oct, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

A fundamental idea in application development, State management is especially important for contemporary online and mobile apps, where dynamic, interactive user experiences necessitate frequent modifications to data and interface components. Fundamentally, it describes how an application maintains and monitors its "state"—the present state of variables, user inputs, retrieved data, and interface conditions—during the course of its lifetime.

This state changes as users engage with the application, and to preserve a consistent user experience, these changes must be mirrored throughout the user interface (UI). Effective state management entails managing the sharing, editing, and synchronization of data among many program components.

From minor, local state modifications (like monitoring a user's input in a form) to intricate global state modifications (like preserving a user's authentication status across several pages), this can take many forms. Applications that lack adequate state management may become challenging to maintain and debug, with poorly coordinated state updates leading to unpredictable behavior or inconsistent data.

These are the following subtopics that we are going to explore:

Why State Management is Important in React?

As the complexity of the application increases, state management in React is essential for maintaining consistency and synchronization between data and the user interface (UI). Because the user interface (UI) in React is a reflection of the underlying state, to ensure a consistent user experience, any changes to the state must be appropriately reflected in the UI. Data inconsistencies or out-of-date user interface elements could result from components that don't update as intended due to improper state management.

Effective state management also avoids problems such as "prop drilling," which involves manually passing data through several levels of components, increasing the complexity and difficulty of maintaining the code. Multiple components frequently need to share or interact with the same data as applications get bigger. tools for state management, like Redux or React's Context API.

React Context API

The React Context API is a way to pass data through your component tree without manually passing props at every level. It allows you to store values that can be accessed by multiple components, avoiding "prop drilling."

Let’s create a Book List example where we manage a list of books and allow users to add new books to the list using the Context API.

1. Create the Context

We create a context using React.createContext() to store the global state and functions.

import React, { createContext, useState } from 'react';

// Create the BookContext

export const BookContext = createContext();

// BookProvider component to provide the context

export const BookProvider = ({ children }) => {
const [books, setBooks] = useState([
{ title: '1984', author: 'George Orwell' },
{ title: 'To Kill a Mockingbird', author: 'Harper Lee' }
]);

// Function to add a new book

const addBook = (title, author) => {
setBooks([...books, { title, author }]);
};

return (
<BookContext.Provider value={{ books, addBook }}>
{children}
</BookContext.Provider>
);
};

2. Consume the Context in Components

We will create two components:

  • BookList: To display the list of books.
  • AddBook: To add a new book to the list.

BookList Component: This component will consume the books array from the context using the useContext hook.

import React, { useContext } from 'react';
import { BookContext } from './BookContext';

const BookList = () => {
const { books } = useContext(BookContext); // Access books from context

return (
<div>
<h2>Book List</h2>
<ul>
{books.map((book, index) => (
<li key={index}>
<strong>{book.title}</strong> by {book.author}
</li>
))}
</ul>
</div>
);
};

export default BookList;

AddBook Component: This component will consume the addBook function from the context to add new books to the list.

import React, { useState, useContext } from 'react';
import { BookContext } from './BookContext';

const AddBook = () => {
const { addBook } = useContext(BookContext); // Access the addBook function
const [title, setTitle] = useState('');
const [author, setAuthor] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
addBook(title, author); // Add the new book
setTitle(''); // Reset the input fields
setAuthor('');
};

return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Book title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="text"
placeholder="Author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<button type="submit">Add Book</button>
</form>
);
};

export default AddBook;

3. Combine Components in the App

We will wrap the BookList and AddBook components inside the BookProvider to give them access to the context.


import React from 'react';
import { BookProvider } from './BookContext';
import BookList from './BookList';
import AddBook from './AddBook';

const App = () => {
return (
<BookProvider>
<h1>My Book List</h1>
<BookList />
<AddBook />
</BookProvider>
);
};

export default App;

This book list example showcases how to manage global state efficiently using the React Context API. It’s simple to implement and suitable for many use cases where the state doesn’t require complex logic or structure.

Redux

Redux is a state management library often used in larger React applications to manage global state in a predictable way. Redux provides a centralized store where all the state lives, and components can dispatch actions to update this state. It is commonly used in complex applications where state needs to be shared between many components.

Let’s use Redux to implement a Book List application. The goal is to manage a list of books, allow users to add new books, and display them using Redux for state management.

1. Defining Actions

Actions in Redux are plain JavaScript objects with a type field (to describe the action) and a payload field (to hold any data the action might need). For the Book List, we’ll define an action to add a book.

// actions.js

// Action type

export const ADD_BOOK = 'ADD_BOOK';

// Action creator to add a book

export const addBook = (title, author) => {
return {
type: ADD_BOOK,
payload: { title, author }
};
};

Here, addBook is an action creator that returns an action object with type: ADD_BOOK and the payload containing the new book’s title and author.

2. Creating a Reducer

Reducers in Redux are functions that take the current state and an action as arguments and return a new state based on the action.

// reducer.js

import { ADD_BOOK } from './actions';

// Initial state of the book list

const initialState = {
books: [
{ title: '1984', author: 'George Orwell' },
{ title: 'To Kill a Mockingbird', author: 'Harper Lee' }
]
};

// Reducer function to handle actions

const bookReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_BOOK:
return {
...state, // Keep the current state
books: [...state.books, action.payload] // Add new book to the list
};
default:
return state; // If action type doesn't match, return the current state
}
};

export default bookReducer;
  • The bookReducer checks the action.type. If it's ADD_BOOK, it adds the new book (found in action.payload) to the books array.
  • The reducer always returns a new state object instead of mutating the old state.

3. Creating the Store

The Redux store holds the global state of the application. We use createStore from Redux to create the store and pass it the reducer.

// store.js

import { createStore } from 'redux';
import bookReducer from './reducer';

// Create the Redux store with the reducer

const store = createStore(bookReducer);

export default store;

Here, the createStore function creates a Redux store using the bookReducer. This store will now manage the state of the books.

4. Connecting Redux to React

Now, we’ll create components to:

  • Display the book list.
  • Add new books to the list.

We’ll use react-redux to connect these components to the Redux store.

Installing React-Redux

First, install the necessary libraries:

npm install redux react-redux

BookList Component: This component will use useSelector to get the book list from the Redux store.

// BookList.js

import React from 'react';
import { useSelector } from 'react-redux';

const BookList = () => {
const books = useSelector((state) => state.books); // Access books from Redux store

return (
<div>
<h2>Book List</h2>
<ul>
{books.map((book, index) => (
<li key={index}>
<strong>{book.title}</strong> by {book.author}
</li>
))}
</ul>
</div>
);
};

export default BookList;

AddBook Component: This component will use useDispatch to dispatch the addBook action and add a new book to the Redux store.

// AddBook.js

import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addBook } from './actions';

const AddBook = () => {
const [title, setTitle] = useState('');
const [author, setAuthor] = useState('');
const dispatch = useDispatch(); // Get dispatch function

const handleSubmit = (e) => {
e.preventDefault();
dispatch(addBook(title, author)); // Dispatch addBook action
setTitle(''); // Clear input fields
setAuthor('');
};

return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="text"
placeholder="Author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<button type="submit">Add Book</button>
</form>
);
};

export default AddBook;

Main App Component: We will wrap our components with the Provider component from react-redux, passing the Redux store as a prop. This allows the components to access the store.

// App.js

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import BookList from './BookList';
import AddBook from './AddBook';

const App = () => {
return (
<Provider store={store}>
<div>
<h1>My Book List</h1>
<BookList />
<AddBook />
</div>
</Provider>
);
};

export default App;

This example shows how Redux manages the state for a Book List application. It demonstrates the benefits of centralized state management, particularly in more complex applications where the state is shared across multiple components.

Recoil

Recoil is a state management library for React that is designed to be simple and scalable. Unlike Redux, Recoil allows you to manage global state but also provides tools to manage derived state and make state updates more efficient. It integrates seamlessly with React hooks like useState and useEffect.

In this example, we will implement a Book List using Recoil, where we can manage a list of books and add new books.

1. Installing Recoil

First, you need to install the Recoil package:

npm install recoil

2. Creating Atoms

Atoms in Recoil represent state that can be shared across components. For the Book List app, we’ll create an atom to store the list of books.

// atoms.js

import { atom } from 'recoil';

// Atom to store the list of books

export const bookListState = atom({
key: 'bookListState', // unique key to identify the atom
default: [
{ title: '1984', author: 'George Orwell' },
{ title: 'To Kill a Mockingbird', author: 'Harper Lee' }
], // initial state
});

Here, the bookListState atom stores the default list of books. It will be used in components to access and update the book list.

3. Creating Components

We’ll create two components:

  • BookList: Displays the list of books.
  • AddBook: Allows users to add a new book to the list.

BookList Component: This component will use useRecoilValue to access the current list of books from the bookListState atom.

// BookList.js

import React from 'react';
import { useRecoilValue } from 'recoil';
import { bookListState } from './atoms';

const BookList = () => {
const books = useRecoilValue(bookListState); // Access book list state

return (
<div>
<h2>Book List</h2>
<ul>
{books.map((book, index) => (
<li key={index}>
<strong>{book.title}</strong> by {book.author}
</li>
))}
</ul>
</div>
);
};

export default BookList;

In this component, useRecoilValue is used to access the bookListState atom, which holds the list of books. It renders each book in the list.

AddBook Component: This component will use useSetRecoilState to update the book list atom by adding new books.

// AddBook.js

import React, { useState } from 'react';
import { useSetRecoilState } from 'recoil';
import { bookListState } from './atoms';

const AddBook = () => {
const [title, setTitle] = useState('');
const [author, setAuthor] = useState('');
const setBooks = useSetRecoilState(bookListState); // Set the book list state

const handleSubmit = (e) => {
e.preventDefault();
setBooks((prevBooks) => [
...prevBooks,
{ title, author }
]); // Add new book to the list
setTitle(''); // Clear input fields
setAuthor('');
};

return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="text"
placeholder="Author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<button type="submit">Add Book</button>
</form>
);
};

export default AddBook;

In this component, useSetRecoilState is used to update the bookListState atom. When the form is submitted, the new book is added to the list.

4. Wrapping the App with RecoilRoot

To use Recoil in your app, you need to wrap your entire app with the RecoilRoot component. This component makes the Recoil state available to all the components in your app.

// App.js

import React from 'react';
import { RecoilRoot } from 'recoil';
import BookList from './BookList';
import AddBook from './AddBook';

const App = () => {
return (
<RecoilRoot>
<div>
<h1>My Book List</h1>
<BookList />
<AddBook />
</div>
</RecoilRoot>
);
};

export default App;

This book list example demonstrates how Recoil can be used for state management in React. Recoil's simplicity and efficiency make it an excellent alternative to Redux, especially for React applications that don’t require as much boilerplate or have complex state logic.

Difference between Context API , Redux and Recoil

Feature

Context API

Redux

Recoil

Core Concept

Context provides a way to pass data deeply through the component tree without manually passing props down at every level.

Redux manages global state via a centralized store with actions, reducers, and strict rules for state mutations.

Recoil provides shared state across components using atoms (state units) and selectors (derived state). Efficient and React-centric.

Use Case

Ideal for small to medium apps where a few global states are needed (e.g., theme, user session).

Ideal for large applications with complex state logic that requires a predictable state flow.

Suitable for both small and large apps, particularly where you need shared or derived state, with minimal boilerplate.

Complexity

Easy to use for simple use cases but becomes complicated with deeply nested state or many providers.

Complex due to the setup of actions, reducers, middleware, and store, especially for async operations

Very simple to set up, even for large applications. Atoms and selectors make state management straightforward.

Boilerplate Code

Very little boilerplate. Just set up Context and Provider.

A lot of boilerplate (actions, reducers, store configuration, action types).

Minimal boilerplate. Only atoms and selectors, similar to useState and useEffect hooks.

Middleware and Side Effects

Lacks built-in middleware. You’ll need to manage side effects (e.g., async operations) using hooks (useEffect).

Supports middleware like redux-thunk or redux-saga to handle side effects and async logic.

Built-in support for handling async logic using Recoil’s selectors, which makes it easy to handle derived or asynchronous state.


Next Article

Similar Reads