Many React apps use a state management library like Redux for managing state. But do you really need one?

In this post, I will describe the pros and cons of using a state management library so that you can decide whether using one is worth it. I will also debunk some common myths about state management and give you my take on what you should do.

Hand drawn sketch of the word state inside a cogwheel

The pros of using a state management library

More performance and convenience when working with global state

Global state is the state that is shared across several React components. Common examples of global state are the user preferences such as the theme and user details such as the username and avatar that are used by several components.

Using the built-in React Hooks (useState and useReducer) to manage this state leads to less-than-ideal results where the state values need to be passed manually between each component using props. The community calls this practice prop drilling and it makes future changes harder since several components need to be modified to accommodate a change to a state value.

import { useState } from 'react';

const GrandParent = () => {
const [inheritance, setInheritance] = useState(1000); // The state lives here
return (
<div>
I am the Grandparent.
<Parent inheritance={inheritance} />
</div>
)
}

// Parent doesn't use the state but still has to accept it to pass it on to the Child
// (a.k.a. prop drilling)
const Parent = ({ inheritance }) => (
<div>
I am the parent
<Child inheritance={inheritance} />
</div>
)

const Child = ({ inheritance }) => (
<div>
I am the child. My inheritance is
{inheritance}. {/* The state is used here */}
</div>
)

Prop drilling could be avoided by using React context but it is not designed for this use case and causes several performance issues where large parts of the app will re-render when the state changes. You can overcome these issues to some extent by using several context providers but it is very tedious.

A state management library addresses both problems. It provides a mechanism to easily read and update state from any component without re-rendering other parts of the app.

Proven patterns

React does not provide a pattern for organizing state. So when working with React state hooks, you have to organize the state yourself. This can be tricky, especially if you are not familiar with React and it is easy to adopt unmaintainable and non-performant patterns.

Most state management libraries provide a pattern for working with state. By adopting that library and the pattern underlying it, you are more likely to create an app that is easier to understand and maintain.

The cons of using a state management library

More concepts to learn

Many state management libraries have their own pattern for managing state. Learning these patterns can be difficult since they consist of many new concepts.

Moreover, not all React developers will be familiar with the concepts of a specific library since they might not have worked with it so far. So when new team members join, it could take them longer to start work since they have to learn the concepts from scratch.

Myths around state management libraries

Boilerplate code

Redux is the most used state management library, partly because it is one of the oldest and the most proven. However, Redux's pattern is very verbose, meaning that a lot of code has to be written to add a new state value or operation. This led Redux and state management libraries as a whole to develop a reputation for needing a lot of boilerplate code.

However, the development experience around using Redux has changed dramatically thanks to Redux Toolkit. Redux Toolkit is a set of helpers and utilities for Redux that automatically generate the code that is needed to use Redux so that it doesn't have to be written manually. For example, this is all you need to start using a single value named count and a single action named increment.

import { createSlice } from '@reduxjs/toolkit';

export const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
// Redux Toolkit uses immer internally so state can be mutated here
increment: (state) => {
state.count += 1;
}
},
selectors: {
getCount: (state) => state.count
}
});

If you want to cut back even more, consider using Zustand instead.

import create from 'zustand';

const useCountStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
}));

Overall, the reputation that state management libraries have gotten for needing a lot of boilerplate code is not valid anymore.

Increased bundle size

Each library you use in a React app increases the size of the Javascript "bundle" and state management libraries are no exception. A larger Javascript bundle increases the app's loading time since the browser needs to download and parse a larger Javascript file.

However, many state management libraries are relatively small compared to the React packages. The react package and the react-dom package add up to 136.5KB (44.4KB gzipped) while Zustand's package is 3.1KB (1.2KB gzipped). (According to bundlephobia for react@18.3.1, react-dom@18.3.1 and zustand@4.5.4) In this case, the effect of including a state management library is barely noticeable.

Admittedly, feature-packed state management libraries like Redux Toolkit are larger (39.5KB - 13.5KB gzipped). But then you should consider the amount of code that you save by using the features of the library. For example, by using Redux Toolkit's RTK query, you will not have to implement a lot of the repetitive logic around interacting with a server thus reducing the size of the Javascript bundle. So the more these features are used, the more they can offset the weight of the library.

My advice

I hope this post has helped you understand the benefits and costs of adopting a state management library.

If you still can't decide what to do, here are my 2 cents.

Prabashwara Seneviratne (bash)

Written by

Prabashwara Seneviratne (bash)

Lead frontend developer