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.
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 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.
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 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.
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.
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.
// 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 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 and 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.
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
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.
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
.
const userInfo = () => {
const userInfo = useRecoilValue(userInfoState);
};
4. React Context API
React Context API 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.
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:
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:
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:
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
:
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 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 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.
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:
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:
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:
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:
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.