Top Five Lightweight State Management Libraries for React
Published: 2023-06-23 02:24:15

by [Blessing Akanimoh Awak](https://blog.openreplay.com/authors/blessing-akanimoh-awak)

<blockquote><em>State management libraries provide an organized manner to handle and update your application’s current data or condition (state). They let you keep track of the state of your application in a consolidated and structured manner, making it simpler to manage, update, and maintain its state. There are many options for React, and this article will focus on the top five lightweight libraries.</em></blockquote>





This article will examine and compare five distinct lightweight libraries for React to determine the optimal choice for your next React application.

## 1. React Hook Form

[React Hook Form](https://blog.openreplay.com/get-started-with-the-react-form-hook/) is a lightweight and performant framework for handling form states in `React`. It takes advantage of React hooks to give a straightforward `API` for constructing reusable forms with little boilerplate code. Using React Hook Form, you can quickly manage form validation, error messages, and submit logic. It’s an excellent solution for creating sophisticated forms in React apps. 

![-](https://blog.openreplay.com/images/top-five-lightweight-state-management-libraries-for-react/images/vMBA4Pq.png)

```javascript
import React from "react";
import { useForm } from "react-hook-form";

function contactForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="name">Name:</label>
      <input type="text" id="name" {...register("name", { required: true })} />
      {errors.name && <span className="error">This field is required</span>}

      <label htmlFor="email">Email:</label>
      <input
        type="email"
        id="email"
        {...register("email", { required: true, pattern: /^\S+@\S+$/i })}
      />
      {errors.email?.type === "required" && (
        <span className="error">This field is required</span>
      )}
      {errors.email?.type === "pattern" && (
        <span className="error">Invalid email address</span>
      )}

      <label htmlFor="message">Message:</label>
      <textarea id="message" {...register("message", { required: true })} />
      {errors.message && <span className="error">This field is required</span>}

      <button type="submit">Submit</button>
    </form>
  );
}

export default contactForm;
```

## 2. Zustand

[Zustand](https://www.npmjs.com/package/zustand) library is a small, fast, and scalable bare-bones state management solution using the simplified black principle, and it has grown in popularity among React developers as a lightweight and efficient way of handling state in their apps. "Zustand" is a German word for "state".

It contains hook API, so it is very easy to consume in React applications. It supports Async methods and easily integrates additional middleware like `immer`, `dev tools`, etc. Easily integrated with other state management libraries like `Redux` & `React Context API`, states can be accessed outside of the React components.

![-](https://blog.openreplay.com/images/top-five-lightweight-state-management-libraries-for-react/images/cKtPTxi.png)

First, create a store. Your `store` is a hook! You make a new copy of the data to change something instead of changing the original. The `set` function makes updating easier by merging changes with existing data.

```javascript
import create from 'zustand'

// create a store to hold our application's state
const useAppStore = create((set) => ({
  loggedInUser: null,
  notifications: [],
  setLoggedInUser: (user) => set({ loggedInUser: user }),
  addNotification: (notification) =>
    set((state) => ({ notifications: [...state.notifications, notification] })),
  clearNotifications: () => set({ notifications: [] }),
}));

```
Then bind your components. Use the hook anywhere; no providers are needed. Select your state, and the component will re-render on changes.

```javascript
// a component that displays the logged in user's name
function userDisplay() {
  const loggedInUser = useAppStore((state) => state.loggedInUser);
  return <h1>Welcome, {loggedInUser.name}!</h1>;
}

// a component that allows the user to log out
function logOutButton() {
  const setLoggedInUser = useAppStore((state) => state.setLoggedInUser);
  return <button onClick={() => setLoggedInUser(null)}>Logout</button>;
}
```

## 3. Recoil

[Recoil](https://recoiljs.org/) is a state management library for React applications that helps you manage and share states in a more efficient and predictable way. It was released to the public by a team at Facebook in May 2020 as an alternative to `Redux` and offers a simpler, more flexible approach to state management.

[Atoms](https://recoiljs.org/docs/basic-tutorial/atoms/) and [Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) are the two main concepts for Recoil. 

`Atoms` are discrete units of state that store pieces of state in react. 

A `selector` is a derived state that passes the current state to a pure function, which then computes a new value based on that state. In other words, a selector is a state representation derived from a specific computation performed on the original state.

![-](https://blog.openreplay.com/images/top-five-lightweight-state-management-libraries-for-react/images/4wlOFeF.png)

```javascript
const boxState = atom({
    key: "box",
    default: {color: #000000}
});
```

You can create, export, and use an atom anywhere in your app. And you use it in the same way that you would use a `useState`. To use `atoms`, you will need to utilize a Recoil hook, such as `useRecoilState`

```javascript
export const Box = ({ id }: { id: number }) => {
  const [box, setBox] = useRecoilState(boxState(id));

  return (
    <div
      className="box"
      onClick={() => {
        setBox({ color: randomColor() });
      }}
      style={{
        backgroundColor: box.color,
      }}
    />
  );
};
```
Recoil can be used to generate a new state from an `atom` using `selectors`. Simply retrieve the values of the `atoms` that you require in your `selector`, and whenever the values of those `atoms` change, the `selector` will rerun, and the components that use it will re-render and display the new value.

```javascript
const userInfoState = selector({
  key: "userInfo",
  get: async ({ get }) => {
    //asynchronous state
    const userId = get(UserIdState);

    const info = await API.userInfo(userId);
    return info;
  },
});
```

To use `selectors`, you will need to utilize a Recoil hook, such as `useRecoilValue`.

```javascript
const userInfo = () => {
  const userInfo = useRecoilValue(userInfoState);
};
```


<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>





## 4. React Context API

[React Context API](https://hy.reactjs.org/docs/context.html) is a built-in React library that allows you to share data across components without having to manually send it down the component tree via props. This can make your code more readable and cleaner. You may establish a context with a value, and any component that requires that value can use the `useContext` hook to obtain it. The Context API may be used to store a variety of data kinds, including themes, user authentication, and even a shopping cart. 

![-](https://blog.openreplay.com/images/top-five-lightweight-state-management-libraries-for-react/images/9GH6ic4.png)

Assume we're developing a website that lists a user's top movies. We want to store the movie list and make it available to all components that use it. Using the Context API, we can build a `movieContext` to hold the list of movies.

First, we'll create a new file called `movieContext.js`. In this file, we'll import the `createContext` function from React and use it to create our context:

```javascript
import { createContext } from 'react';

export const movieContext = createContext();
```

Next, We'll use a `movieProvider` component with the React Context API to manage the movie list. We'll store the list using `useState` and fetch it using `useEffect`. This allows us to access the list across the app without passing it down through the component tree:

```javascript
import { useState, useEffect } from "react";

export const movieProvider = ({ children }) => {
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    fetch("https://api.example.com/movies")
      .then((response) => response.json())
      .then((data) => setMovies(data));
  }, []);

  return (
    <movieContext.Provider value={movies}>{children}</movieContext.Provider>
  );
};
```
Lastly, we'll encase our entire app in the `movieProvider` component to make the movie state accessible to all components:

```javascript
import { movieProvider } from "./movieContext";

function App() {
  return (
    <movieProvider>
      <div className="App">
        <Header />
        <movieList />
      </div>
    </movieProvider>
  );
}
```
Any component that requires access to the list of movies may now use the `useContext` hook to retrieve the information from the `movieContext`:

```javascript
import { useContext } from "react";
import { movieContext } from "./movieContext";

export const movieList = () => {
  const movies = useContext(movieContext);

  return (
    <ul>
      {movies.map((movie) => (
        <li key={movie.id}>{movie.title}</li>
      ))}
    </ul>
  );
};
```

## 5. Redux Toolkit

Think of [Redux](https://redux.js.org/) as a centralized brain for your application, where all the important decisions and actions are made. It keeps track of the current state of your application and makes it easy to update and access that state from anywhere in your code. It runs in different environments (client, server, and native). 

[Redux Toolkit](https://redux-toolkit.js.org/) is like having a personal assistant who takes care of all the small details for you. It provides you with pre-built functions and guidelines that simplify the process of writing Redux code, so you can focus on building your application's features instead of worrying about the nitty-gritty details of Redux. 

![-](https://blog.openreplay.com/images/top-five-lightweight-state-management-libraries-for-react/images/DQ23Rrw.png)

Assume you're developing a shopping cart application and need to handle the status of the goods in the cart. You may use the Redux Toolkit to define a `slice` of the `state` that handles the cart items. Here's some code to get you started:

```javascript
import { createSlice } from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: "cart",
  initialState: [],
  reducers: {
    addItem: (state, action) => {
      state.push(action.payload);
    },
    removeItem: (state, action) => {
      const index = state.findIndex((item) => item.id === action.payload.id);
      if (index !== -1) {
        state.splice(index, 1);
      }
    },
  },
});

export const { addItem, removeItem } = cartSlice.actions;

export default cartSlice.reducer;
```
To use this cart slice in your Redux store, you would import it and add it to the `combineReducers` function:

```javascript
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./cartSlice";

const store = configureStore({
  reducer: {
    cart: cartReducer,
  },
});

export default store;
```
Now, to add an item to the cart, you can dispatch the `addItem` action:

```javascript
import { useDispatch } from "react-redux";
import { addItem } from "./cartSlice";

function AddToCartButton({ item }) {
  const dispatch = useDispatch();

  const handleClick = () => {
    dispatch(addItem(item));
  };

  return <button onClick={handleClick}>Add to Cart</button>;
}
```
Finally, to display the items in the cart, you can use the `useSelector` hook to get the cart state from the Redux store:

```javascript
import { useSelector } from "react-redux";

function Cart() {
  const cartItems = useSelector((state) => state.cart);

  return (
    <div>
      <h2>Cart</h2>
      <ul>
        {cartItems.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}
```

## Conclusion

Every project is unique and has its own set of dependencies and business use cases, and fits with one solution over another. All of the methods listed above seek to handle the same problem in various ways.

* React Hook Form: Suitable for constructing robust forms with features like automated form validation and error handling. Well-documented and beginner-friendly.

* Zustand: A lightweight state management solution ideal for small to medium-sized apps that don't require a full-featured solution like Redux.

* Recoil: A more capable state management option designed for managing complex states in larger applications.

* React Context API: Provides good performance for simple applications but may not scale well for larger or more complicated projects. Testing and measuring performance is crucial in determining its suitability.

* Redux Toolkit: Offers speed improvements through features like memoized selectors and improved Redux store updates. Effective for handling state in React applications.

Regardless of the state management library you select, you'll need to analyze your development team's skills and overall comfort with React.