You Don’t Need Redux — 3 Simple Alternatives for State Management in React

You Don’t Need Redux — 3 Simple Alternatives for State Management in React

https://medium.com/@jonnystartup
Photo by Patrick Fore on Unsplash

If you’re writing a React app, you’ll need some strategy to manage state — how you store and modify the app’s data. Almost React developer I ask uses Redux for state management, and almost every one I ask has some issues with it. The good news is that there are lots of great alternatives to explore — in this article, we’ll look at a couple of different alternatives and see how they stack up against Redux.

What is state management?

If you’re building an app in React, at some point you’ll need to start managing data: doing thing like taking user input, computing values, fetching and displaying data from a server, etc. Abstractly speaking, the getting and setting of any kind of data involved in all of these examples is referred to as state management.

Your state management strategy helps answer questions like where to store your data, how to access that data, what the interfaces and rules look like to modify that data. And while that’s the core function of state management libraries, there are many other periphery features they may provide: data validation & type checking, network request & asynchronous data management, undo/redo, etc.

The first kind of state management you might learn about with React is the useState hook, which allows you to store and read a value within an individual component. For example, if you have a form with a checkbox, you might useuseState to manage a checked boolean value and a setCheckedfunction to modify the value.

But what about when your state needs to live outside of the component and is part of your larger application? At that point, you’ll need a different solution — one that can help with your applications “big picture”.

Redux: the Elephant in the Room

Due to its popularity, the “default” choice for state management is often a powerful library called Redux. If everybody else has chosen Redux, it seems like a safe choice, right? The answer is maybe. Redux is great, but it does come with it’s own set of baggage.

The core concepts around managing state via the Redux interface involve creating actions, simple objects that describe the state change; and reducers, which take the current state and an action in order to generate a new state. The actions are sent to the state via a call to dispatch. There’s a lot more to it than that, but those are the concepts you need to get started.

If you haven’t used Redux before, I’d encourage you to take a look at the Quick Start guide to see how a basic Redux store operates and how these core concepts are put into action.

While Redux is very powerful, here are some of the things I don’t love about it:

  • It’s complicated: the various concepts that make Redux so powerful also mean that you’re dealing with a lot of abstractions to handle simple scenarios. Encapsulating state updates as actions have an advantage of more constrained and more semantic state manipulation, at the cost of additional complexity.
  • Lots of boilerplate: related to the first point, Redux requires you to write a lot more code to handle simple use-cases. You might find yourself missing the simplicity of simple state management provided by useState — [value, setValue] = React.useState()
  • Middleware: another complicating factor of Redux is that for some common use cases, you may find yourself needing to integrate and configure various pieces of Redux middleware, essentially plugins. This is both a testament to the power and flexibility of Redux, but also to its complexity — you may find yourself wishing that some of these features were built-in and a little more “opinionated”.

Alternative #1: React Contexts

The simplest alternative to Redux is to use React’s built in Contextsfunctionality. One of the many important pieces of functionality state management libraries provide is the ability to access content across multiple distant components. This is precisely what React Contexts provides, without any of the other features of a state management library.

React Contexts: You have one job

In the example below, we show a common context use case: passing a theme value (and a way to change it) through an app without explicitly prop drilling the value through to every component that needs it. First, we create a context at a high-level of the app, and pass in the data we want to be available elsewhere via a Provider. The data can be anything we want (even something not related to state managment), but in this case, we pass in an array containing a color, and a color setter function.

const ThemeContext = React.createContext(["", () => {}]);

const App = () => {
 const [color, setColor] = React.useState("#000")
return (<ThemeContext.Provider value={[color, setColor]}>        
    <ComplicatedApplicationWithManyScreens />
  </ThemeContext.Provider>)
}

Elsewhere in our app, if we need to access (or change) the color, we can do so with a Consumer:

const ThemedScreen = () => {
  return (<ThemeContext.Consumer>
    {([color, setColor]) => (<Screen color={color} setColor={setColor} />)}
  </ThemeContext.Consumer>)
}

Note that our ThemedScreen component can be anywhere in the view hierachy and does not need to be passed any special props — it pulls them off of the context consumer and uses them however it needs to.

When to use React Contexts

When your state management needs are extremely simple and you don’t need any of the higher level functionality provided by other libraries, React Context provide a simple, dependency-free way to manage state.

Additionally, even if you do use some other state management library, React Contexts are a great tool to use for “niche” state management use-cases that don’t follow the same pattern as the rest of the state management in your app. The example above, theme management, is an excellent use case for React Contexts because it typically does not require any of the more advanced functionality other kinds of state management libraries provide: there are generally not many instances of validation, network requests, or complicated data transforms.

When not to use React Contexts

As mentioned above, React Contexts are best for very simple use cases. If you do need more advanced functionality, you’ll end up building it yourself on top of contexts and may quickly realize that other approaches will do a better and faster job of solving those problems.

An additional risk of using contexts is that it does not help you with your data architecture: some people may be tempted to put more and more application state into a single context, but this is generally a bad idea. As the state becomes more complicated, application performance will suffer due to excessive re-renders. This is a risk with any state management solution, but is a particularly easy trap to fall into with contexts — the others tend to provide better guidance around structuring data.

I recommend using contexts for these targeted use-cases like theming if they’re a good fit, and possibly as an initial state management approach in the early stages of app development, but be aware of the limitations and be ready to switch when you need more advanced state management functionality.

Alternative #2: Recoil

Recoil is an up-and-coming alternative to Redux being developed by Facebook. Recoil is my personal choice in large part because of its simplicity: it essentially enables an interface similar to the useState hook, but works across components via a high level RecoilRoot wrapper component (essentially a specialized context as shown above).

The first step is using Recoil is to define the various pieces of application state called atoms:

const colorState = Recoil.atom({
  key: 'colorState',
  default: "#f00",
});

Once configured, Recoil values and setters are accessed using useRecoilState:

const ThemedScreen = () => {
  const [color, setColor] = Recoil.useRecoilState(colorState)
  return <Screen color={color} setColor={setColor} />
}

Beyond the core state management functionality, Recoil also provides support for more advanced features such as persistence, asynchronous data (like network requests) and customizable data management side-effects (i.e., perform other actions when data changes).

When to use Recoil

Personally, Recoil is my default go-to on new projects. It provides the right balance of simplicity and functionality for most projects, especially in the early stages. The familiar useState-style interface makes it easy-to-use and highly approachable for developers new to the library. It also takes very little code-overhead to get started in using it.

When not to use Recoil

While Recoil provides a ton of great features right out of the box, it is on the more minimal side of the state management spectrum. If you do like some of the features of Redux but want to avoid the baggage, the third alternative may hit the sweet spot between simplicity and expressiveness.

Alternative #3: Zustand

Zustand is an interesting alternative because it’s provides some of the simplicity of Recoil, but with more of the expressiveness of Redux.

Zustand certainly wins the “most adorable logo” part of the competition

Like Recoil, Zustand features a greatly simplified state management experience by default, but also allows (optionally) for more expressive action- and reducer-like functionality that you’d find in Redux. This code sample adapted from the documentation shows how it works when setting up a store:

const useStore = create((set) => ({
  color: "#f00",
  setColor: (color) => set((state) => ({ color })),
  setDarkMode: () => set((state) => ({ color: "#000" }))
}))

And then using the store from a component:

function ThemedScreen() {
  const {color, setColor} = useStore((state) => state)
  return <Screen color={color} setColor={setColor} />
}

Note that rather than setting the store value directly, the setColor call behaves like a reducer in Redux, taking in the current state and transforming it to the next state. In this example we also provide a more semantic setDarkMode function just to show that Zustand can support a more typical Redux usage pattern.

Like Recoil, Zustand also provides support for async actions, persistence and more via included and mostly preconfigured middleware. And with some simple wrappers (or other pieces of middleware), it can also provide an interface just like using Redux actions.

When to use Zustand

If you do like some parts of Redux, but not necessarily the whole thing, Zustand can effectively provide Redux-like functionality where you need it, but without the heavy boilerplate code overhead.

When not to use Zustand

Because it is sits in between Recoil and Redux in terms of functionality and complexity, there may be times where it just makes more sense to choose one of those tools directly instead of trying to emulate it with Zustand.

Other Alternatives

Though the libraries discussed in this article are excellent choices for state management in React, there are many other alternatives available. Honorable mentions go to some other libraries that I’ve not personally used, but which get great reviews: MobX, Jotai and Valtio.

For a more comprehensive deep-dive on some of the tradeoffs and considerations around state management, such as performance, memory usage and immutable vs mutable approaches, I highly recommend reading this article.

Conclusion — To Redux or Not to Redux?

When comparing technologies, I always make a point to say that it’s not about which technology is better, but about which technology is better for a specific use case. In other words, it’s all about trade-offs.

In this article I’ve laid out some of the tradeoffs between Redux and other state management libraries so that you can make a more informed decision. Redux is certainly a powerful and mature library for state management, but you should be aware of the alternatives and decide for yourself which one is the best fit for your application. I won’t say I’ll never use Redux again, but I’m much more likely to reach for Recoil or Zustand to solve these state management problems before reaching for the big guns.


Report Page