When I started working with React in 2016, Redux was the go-to option for managing state in a React app. But now there are many other libraries and patterns for managing state. Each of them make bold claims as to why they are the best. So I thought I would try several of them to see what the fuss is about.

After going through my notes, I came up with a short list of state management libraries which I think are worth trying. At the end of this post, I will share this list with you. But first, let's look at how we ended up with all of them.

A brief history of state management libraries

The problem with React state

One of React's defining features is its one-way data binding. With it, we don't have to manage the DOM manually. We can change the props or the state of React components and React will automatically calculate the markup and update the DOM to display it in the browser.

React does not have any restrictions on how you can use state. This leaves you in charge of using state in a manageable way and that is a tricky thing to do. Especially when you are not familiar with React and when the app grows beyond a few components. In the first React app I built, I didn't think about how I was using state. I used it whenever and wherever I needed it and then passed around values and callback functions to wherever I also needed it. It was a mess.

Flux

A year after React was initially announced and open-sourced at JSConf 2013, Facebook introduced the Flux architecture for React apps. Instead of directly updating state values, Flux advocated for sending "Actions" via "Dispatchers" to "Stores" that could be bound to different components ("Views"). This way, components only needed to read and update the Store instead of changing other components' state.

Diagram showing the data flow in the Flux architecture
Flux architecture

Redux

Shortly afterward, Dan Abramov was looking for ways to improve the developer experience of building React apps. After building React hot loader for viewing changes to React code without refreshing the browser, he discovered that it didn't work with Flux. So he built his own implementation. While doing so, he simplified the Flux pattern by having "Reducers" instead of Dispatchers and Stores and a single Reducer instead of several Stores. This was Redux. (Reducers + Flux)

Dan Abramov introducing Redux at React Europe 2015
Dan Abramov introducing Redux at React Europe 2015

MobX (Previously MOBservable)

Around the same time, Michel Weststrate was starting work on a visual editor. He was not happy with React's performance when thousands of elements had to be updated simultaneously. To solve this issue, he turned to "Observables" which was a concept used by several other UI frameworks. By default, React re-renders all child components when a component's state changes. By making the data model observable, components could observe and update only when specific parts of the data model change. Eventually, he released his implementation of Observables as a library called MOBservable. This later became MobX.

Diagram showing the data flow in MobX
Data flow in MobX

The de-facto standard

Redux and MobX were some of the earliest state management libraries for React. Of the two, Redux became the de-facto standard for managing state in a React app (perhaps because Redux was based on Flux which was endorsed by Facebook and because the Flux library was more like a proof of concept). Since Redux advocated for storing the entire application state inside a single Redux store, everything from API responses to form inputs and user preferences were stored and managed using Redux.

One of the most disliked aspects of Redux was the amount of boilerplate code it required. To add a new piece of state, Redux needed you to create an action type, an action creator, a new entry in the reducer and a new selector. Fetching data from an API required even more boilerplate since each state of the operation (i.e. loading, success, error) required each of the above.

Relay

When Facebook introduced Relay in 2015, they introduced a way for React components to declaratively state the data they required. You could write a GraphQL query, bind it to a component and let Relay handle the process of loading it without writing the logic yourself. It also allowed query results to be cached so that data could be shared across several components without another trip to the server.

So Relay provided many features that would have had to be implemented manually on Redux. It kick-started a new class of libraries for React that handled data fetching and submission. With them, the amount of state that had to be managed was reduced substantially.

// A short example showing Relay in action
// Source: https://web.archive.org/web/20151009022954/http://facebook.github.io/relay/docs/thinking-in-relay.html

// Usage: `<Story story={ ... } />`
class Story extends React.Component { ... }

// "Higher-order" component that wraps `<Story>`
var StoryContainer = Relay.createContainer(Story, {
fragments: {
// Define a fragment with a name matching the `story` prop expected above
story: () => Relay.QL`
fragment on Story {
text,
author {
name,
photo
}
}
`

}
})

React Hooks

The next major change to state management libraries came from the introduction of React Hooks in 2019. Hooks provided a way to share stateful logic across components. Until now, state management used Higher order components (HOCs) to interact with components (e.g. Redux's connect HOC). Many eventually switched to using Hooks but several Hook-centric alternatives also appeared. (e.g. Zustand, Recoil, Jotai and Valtio) Soon after, the Redux team also introduced Redux toolkit to help developers use Redux with less boilerplate.

The libraries that I want to take a look at

With that brief history lesson out of the way, here are the state management libraries that I want to take a closer look at.

I chose these libraries because they are actively being used by the community and because each of them has different philosophies and patterns.

I planned to refactor Timo to use each of these libraries to see what each of them is like. But now I realize that Timo doesn't have a lot of client state. It mostly deals with server state by interacting with an API to change the state on the server side. So in the next monthly post, I will clean up Timo's server state management by using Tanstack query and add some more features to give us more client state to play with.

Prabashwara Seneviratne

Written by

Prabashwara Seneviratne

Author. Lead frontend developer.