State of React State Management for 2019 (Updated 2021)

Observing state management in the wild… and the Store is open for business! 🐯

Jonathan Saring
Bits and Pieces

--

State management has always been a vital yet somewhat dreadful aspect for working with React. Recently I’ve had the pleasure of talking to a few R&D teams, each of whom had an entirely different opinion about this topic.

From complaints about Redux making it hard to keep components self-contained to experimenting with the new Context API, most are constantly evaluating for the right solution to pick for their team.

In this post, I’ll review the state of React state management for the upcoming year, and some popular ways for managing your component states in React. Hopefully, this can help your team save some valuable time, dig deeper and make the right choice. Let’s dive in!

Tip: Reuse your components using Bit (GitHub) to easily share React components across projects and teams. It helps you reuse and share components between applications, without overhead, and helps your team build faster together. Give it a try, it’s free.

Read:

-> How We Build a Design System

-> How We Build Micro Frontends

-> Watch 5 min demo

React Component State: back to basics

React itself provides some useful methods for setting component states using setState() and adding a “local state” to a class.

With this option, you can call these methods for your components. setState() tells React that this component and its children (sometimes delayed and grouped into a single batch) should be re-rendered with the most updated state, often bases on user-triggered events. setState() will always lead to a re-render as long as an update is available (shouldComponentUpdate()). By adding a “local state” to a class, you can move data from the props themselves onto a state which can be updated.

Managing States with nothing but React itself is entirely possible, but can quickly become ineffective due to scaling complexity and performance issues (dependancies, redundant props, complex trees, etc). So, let’s move on to review React’s Context API- a feature built to solve exactly that.

React Context API (v16.3.0+): A promise of a better future?

The new React Context API, officially recommended from version React’s version 16.3.0, solves a major problem when setting component states: it lets you pass data through the component tree without having to manually pass props at every level of the tree even when not needed (prop drilling).

Using the context API you can share values between components without having to pass props. So far, the React team recommends using it to “share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language”. You might still need to add some code to compose providers and consumers (see this library), but no doubt this new “first-class” feature in React is a game changer.

A replacement to Redux? maybe. Here’s are a couple of short intros and tutorials to Context API and how to convert from Redux:

Hooks!

The new stable hooks introduced in React 16.8.0. let you create a custom hook to manage states inside functional components. Hooks states work pretty much like like class component states, while every instance of the component has its own state. This can be done with our without Context-API. You can use tools like Bit to easily share a reusable Hooks collection, and use them across your apps to speed and simplify the development of components.

Redux: It’s global but it works and there’s middleware!

Redux example

We’ve finally arrived to the very (very) popular, sometimes dreaded, always to be talked about Redux. Above all, Redux favors predictability and control over your component states, making it a great choice for building consistent, cross-environment, testable applications with React.

Redux is only a set of helpers to “mount” reducers to a single global store object. You can use as little, or as much of Redux, as you like.” — Dan Abramov

The Redux store, which is basically an object with some methods, works as a unified place to hold your application’s entire state tree. react-redux makes it simple to map your redux state onto React components (via connector functions, which are higher-order components on their own).

Redux-thunk, Redux-saga and Redux-observable are useful for working with asynchronous stuff which are less optimal when handled by Redux alone.

The result is a very practical way of making states predictable and immutable in React, with a time-travel option, state and action logging for debugging, simple to test React applications which work very well in production.

Although tempting, beware not to throw too much stuff into Redux. To some degree, this goes against the principle of self-contained components in React.

This can be particularly appealing for younger developers. Some claim that this approach might also -counter-intuitively- make it difficult to debug and control state updates at scale, and can cause trouble getting a grip on what’s going on and why it’s happening. While Redux is the de-facto solution for State management in React, you might not really need it.

Unstated: This is so elegant ✨

Here’s a mind-bender: the fact is that it’s hard to share states between components is intentional. Yes. It’s part of making components self-contained and reusable. Trying to tie them into one organism via states by applying some external methods and architectures goes against this principle.

That’s the basic perception behind “unstated”, which led it to try and replicate parts of React’s component state API while sharing it across components. How does that work? through React’s new Context API itself. Interesting, right?

The state is managed in a “component-like” container (with state object methods). Container is a very simple class which is meant to look just like React.Component but with only the state-related bits: this.state and this.setState. The later is the focal point of this entire project, which makes stateless a “wow” moment solution built on top of React’s integral design.

Although a smart, elegant choice, the eco-system around it isn’t as mature as that of Redux, which might make you miss some useful middleware.

You can learn more about recommended use cases and structure here, and here’s a designated debugging library for debugging state containers.

Mobx: The road less traveled

“Anything that can be derived from the application state, should be derived. Automatically.”

Mobx isn’t as popular as Redux, but some consider it a preferable choice due to its basic philosophy and architecture. Mobx lets you store state (even in separation) in any data structure such as objects, arrays and classes in cyclic data structures or references. Updates can then be automated.

appState.resetTimer = action(function reset() {
appState.timer = 0;
});
setInterval(action(function tick() {
appState.timer += 1;
}), 1000);

Making these objects observable and mutable means you can automatically track and update the data as needed. Making (stateless function) React components observable also makes them, well, more reactive. If done right, this philosophy can make for a beautiful scalable architecture.

However, some feel that at scale this approach can compromise control over your app and impair your development and maintenance workflow.

So, it is a better choice than Redux? depends on what you’re looking for, the structure of your app and the key factors you value for managing states. Please feel free to comment below and suggest insights from your experience.

  • Also see:

Apollo Link State: One query to rule them all?

import { withClientState } from 'apollo-link-state';

// This is the same cache you pass into new ApolloClient
const cache = new InMemoryCache(...);

const stateLink = withClientState({
cache,
resolvers: {
Mutation: {
updateNetworkStatus: (_, { isConnected }, { cache }) => {
const data = {
networkStatus: {
__typename: 'NetworkStatus',
isConnected
},
};
cache.writeData({ data });
return null;
},
},
}
});

Apollo’s client makes it simpler to manage remote data from an external API. Apollo’s link state makes it easier to manage your application’s state with Apollo, so you don’t have to use Redux or Mobx alongside Apollo.

Instead, you can use the Apollo Client cache as your single source of truth that holds all of your local data alongside your remote data. The local state can then be accessed using GraphQL queries and mutations just like server data.

The Store debugging workflow is also supported by Apollo Dev-Tools. If you are heavily invested in Apollo to query remote data, this is a serious option to consider for your local data as well. Here’s a nice tutorial to get the idea.

  • Also note Relay — built at Facebook (originally for React Native), and is still in use to aggregate queries. With the new Apollo-link-state and React’s Context-API, it’s an ongoing debate if and what is the right place for Relay in today’s ecosystem. Feel free to learn more in these links.

Honorable mentions

Cool stuff: React State Museum, underground state manager and more :)

--

--

I write code and words · Component-driven Software · Micro Frontends · Design Systems · Pizza 🍕 Building open source @ bit.dev