Cover image
Web Front-end
10 minute read

The Best React State Management Tools for Enterprise Applications

State management in React has been a hotly debated topic for years, yet little attention seems to be paid to enterprise-level applications and their specific requirements. Let’s take a closer look and compare three of the most popular state management tools available today.

Editor’s note: This article was updated on October 17, 2022, by our editorial team. It has been modified to use embedded code demos, reference more recent data, and align with our current editorial standards.

Developers of enterprise-level React applications know how crucial state management is to a coherent end-user experience.

However, the user is not the only one affected by state management. React developers create and maintain state. They want state management to be simple, extendible, and atomic. React has moved in this direction by introducing hooks.

Problems may arise when the state should be shared among many components. Engineers have to find tools and libraries that suit their needs, yet at the same time meet high standards needed for enterprise-grade apps.

In this article, I analyze and compare the most popular libraries and pick the most appropriate one for React state management in an enterprise-level application.

Built-in React State Management Capabilities

React has an excellent tool for providing data across multiple components. The primary goal of Context is to avoid prop drilling. Our goal is to get an easy-to-use tool to manage the state in various scenarios likely to be encountered in enterprise applications: frequent updates, redesigns, the introduction of new features, and so on.

The only advantage of Context is that it doesn’t depend on a third-party library, but that can’t outweigh the effort to maintain this approach.

While all this is theoretically doable with Context, it would require a custom solution that requires time to set up, support, and optimize. The only advantage of Context is that it doesn’t depend on a third-party library, but that can’t outweigh the effort to maintain this approach.

As React team member Sebastian Markbage mentioned, the Context API was not built and optimized for high-frequency updates but rather for low-frequency updateslike theme updates and authentication management.

Examining Existing React State Management Libraries

There are dozens of state management tools on GitHub (e.g., Redux, MobX, Akita, Recoil, and Zustand). However, taking each of them into consideration would lead to endless research and comparisons. That’s why I narrowed down my selection to the three main competitors based on their popularity, usage, and maintainer.

To make the comparison explicit, I used the following quality attributes:

  • Usability
  • Maintainability
  • Performance
  • Testability
  • Scalability (works with the same performance on the bigger states)
  • Modifiability
  • Reusability
  • Ecosystem (has a variety of helper tools to extend the functionality)
  • Community (has a lot of users and their questions are answered on the web)
  • Portability (can be used with libraries/frameworks other than React)

Redux

Redux is a state container created in 2015. It became wildly popular because:

  • There was no serious alternative when it launched.
  • It provided separation between state and actions.
  • react-redux magic enabled straightforward state connection.
  • The co-creator of the Redux library is acclaimed Facebook developer and React core team member Dan Abramov.

Animation showing the progression of states and actions from and to the reducer, using Redux.

You have a global store where your data lives. Whenever you need to update the store, you dispatch an action that goes to the reducer. Depending on the action type, the reducer updates the state in an immutable way.

To use Redux with React, you need to subscribe the components to the store updates via react-redux.

Redux API Example

Slices are the fundamental part of the Redux codebase that differentiates it from the other tools. Slices contain all the logic of actions and reducers.

// slices/counter.js
import { createSlice } from "@reduxjs/toolkit";

export const slice = createSlice({
  name: "counter",
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  }
});

export const actions = slice.actions;
export const reducer = slice.reducer;


// store.js
import { configureStore } from "@reduxjs/toolkit";
import { reducer as counterReducer } from "./slices/counter";

export default configureStore({
  reducer: {
    counter: counterReducer
  }
});


// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)


// App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { actions } from "./slices/counter";

const App = () => {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <div>
        <button onClick={() => dispatch(actions.increment())}>Increment</button>
        <span>{count}</span>
        <button onClick={() => dispatch(actions.decrement())}>Decrement</button>
      </div>
    </div>
  );
};

export default App;

Quality Attributes

  • Usability. Redux became very simple with the introduction of the official toolkit package. You create a slice (a combination of the initial state, reducers, and actions), pass it to the store, and access it in a component via hooks. The addition of RTK Query made Redux Toolkit even more usable by taking care of standard data querying.
  • Maintainability. Redux is simple. It doesn’t require deep knowledge to understand how to enhance or repair something.
  • Performance. The primary performance influencer with Redux is the software engineer. Redux is a straightforward tool without much logic. If you see that state updates are slow, you can follow the official guidelines to make them faster.
  • Testability. Redux consists of pure functions (actions and reducers), making it great for unit testing. It also provides the mechanism to write integration tests where the store, actions, and reducers work together.
  • Scalability. By default, Redux has one global state, making it hard to scale. However, the library redux-dynamic-modules enables the creation of modular reducers and middleware.
  • Modifiability. Customizing Redux is an effortless affair because it supports middleware.
  • Reusability. Redux is framework-agnostic, so it is very good at reusability.
  • Ecosystem. Redux offers a giant ecosystem of helpful add-ons, libraries, and tools.
  • Community. Redux, the oldest state management library in our comparison, has amassed a large community with a significant knowledge base. There are ~34,000 questions (~26,000 answered) with the redux tag on Stack Overflow.
  • Pulse. Redux is still updated and maintained regularly, but development has slowed overall as the project has matured

MobX

MobX is another relatively old library with ~25,800 stars on GitHub. What sets it apart from Redux is that it follows the OOP paradigm and uses observables. MobX was created by Michel Weststrate and it’s currently maintained by a group of open-source enthusiasts with the help of Boston-based Mendix.

Diagram depicting state management using MobX, from actions through observable states and computed values to side effects.

In MobX, you create a JavaScript class with a makeObservable call inside the constructor that is your observable store (you can use @observable decorator if you have the appropriate loader). Then you declare properties (state) and methods (actions and computed values) of the class. The components subscribe to this observable store to access the state, calculated values, and actions.

Another essential feature of MobX is mutability. It allows updating the state silently in case you want to avoid side effects.

MobX API Example

A unique feature of MobX is that you create almost pure ES6 classes with all the magic hidden under the hood. It requires less library-specific code to keep the concentration on the logic.

// stores/counter.js
import { makeAutoObservable } from "mobx";

class CounterStore {
  value = 0;

  constructor() {
    makeAutoObservable(this);
  }

  increment() {
    this.value += 1;
  }

  decrement() {
    this.value -= 1;
  }
}

export default CounterStore;


// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "mobx-react";
import App from "./App";
import CounterStore from "./stores/counter";

ReactDOM.render(
  <Provider counter={new CounterStore()}>
    <App />
  </Provider>,
  document.getElementById("root")
);


// App.js
import React from "react";
import { inject, observer } from "mobx-react";

const App = inject((stores) => ({ counter: stores.counter }))(
  observer(({ counter }) => {
    return (
      <div>
        <div>
          <button onClick={() => counter.increment()}>Increment</button>
          <span>{counter.value}</span>
          <button onClick={() => counter.decrement()}>Decrement</button>
        </div>
      </div>
    );
  })
);

export default App;

Quality Attributes

  • Usability. An observable store is the single entry point for state management. It makes usage of MobX simple because you have the only place to modify.
  • Maintainability. It’s a considerable downside. Without knowledge of RxJS API, you won’t be able to achieve the desired result. Using MobX in a poorly qualified team may lead to state inconsistency troubles.
  • Performance. MobX consists of independent stores and enables you to subscribe to the only ones you need. It’s very effective.
  • Testability. Observable stores are plain JavaScript objects with reactive functionality hidden inside. Testing is the same as for any other JavaScript class.
  • Scalability. Observable stores are split logically; there is no difficulty in scaling MobX.
  • Modifiability. MobX allows creating custom observables with modified behaviors. In addition, there is a concept called reactions. Reactions model automatic side effects. These things make MobX very customizable.
  • Reusability. MobX is agnostic of frameworks, so it is very good at reusability.
  • Ecosystem. There are hundreds of extensions available for MobX.
  • Community. MobX has a lot of devoted fans. There are ~1,800 questions (~1,100 answered) with the mobx tag on Stack Overflow.
  • Pulse. MobX is updated and maintained regularly and, like Redux, has reached a stage of maturity.

Recoil

Recoil is a relative newcomer, the latest brainchild of the React team. The basic idea behind it is a simple implementation of missing React features like shared state and derived data.

You might be wondering why an experimental library is reviewed for enterprise-level projects. Recoil is backed by Facebook and used in some of its applications, and has brought an entirely new approach to sharing state in React. I’m sure that even if Recoil is deprecated, another tool that follows the same path, like Jotai, will gain traction.

Recoil is built on top of two terms: atom and selector. An atom is a shared-state piece. A component can subscribe to an atom to get/set its value.

Diagram depicting state management with Recoil, showing how components can subscribe to an atom to retrieve or set its value.

As you see in the image, only the subscribed components are rerendered when the value changes. It makes Recoil very performant.

Another great thing Recoil has out of the box is the selector. The selector is a value aggregated from an atom or other selector. For consumers, there is no difference between atom and selector—they just need to subscribe to some reactive part and use it.

Diagram illustrating the use of selectors in Recoil, their relation to atoms, and changes caused by different values.

Whenever an atom/selector is changed, the selectors that use it (i.e., are subscribed to it) are reevaluated.

Recoil API Example

Recoil’s code is considerably different from its competitors. It’s based on React hooks and focuses more on the state structure than on mutating the state.

// atoms/counter.js
import { atom } from "recoil";

const counterAtom = atom({
  key: "counter",
  default: 0
});

export default counterAtom;


// index.js
import React from "react";
import ReactDOM from "react-dom";
import { RecoilRoot } from "recoil";
import App from "./App";

ReactDOM.render(
  <RecoilRoot>
    <App />
  </RecoilRoot>,
  document.getElementById("root")
);


// App.js
import React from "react";
import { useRecoilState } from "recoil";
import counterAtom from "./atoms/counter";

const App = () => {
  const [count, setCount] = useRecoilState(counterAtom);

  return (
    <div>
      <div>
        <button onClick={() => setCount(count + 1)}>Increment</button>
        <span>{count}</span>
        <button onClick={() => setCount(count - 1)}>Decrement</button>
      </div>
    </div>
  );
};

export default App;

Quality Attributes

  • Usability. Recoil is one of the easiest tools to use because it works like useState in React.
  • Maintainability. All you have to do in Recoil is maintain selectors and hooks inside the components—more value, less boilerplate.
  • Performance. Recoil builds a state tree outside of React. The state tree enables you to get and listen to only the things you need, not the whole tree’s changes. It’s also well-optimized under the hood.
  • Testability. Recoil provides a mechanism for testing its atoms and selectors.
  • Scalability. A state that splits into multiple independent pieces makes it particularly scalable.
  • Modifiability. Recoil is only responsible for storing values and their aggregations. It has no data flow so it can be customized easily.
  • Reusability. Recoil relies on React. It can’t be reused elsewhere.
  • Ecosystem. There is no ecosystem for Recoil at the moment.
  • Community. Recoil is too fresh to have a big community. There are ~200 questions (~100 answered) with the recoiljs tag on Stack Overflow.
  • Pulse. Recoil is updated frequently and, compared to Redux and MobX, has the most open issues on GitHub.

Which Tool Results in the Best State Management for React?

These React global state management libraries offer different pros and cons when it comes to enterprise-grade apps.

Recoil is young and fresh but has no community nor ecosystem at the moment. Even though Facebook is working on it and the API seems promising, a huge React application cannot rely on a library with weak community support. In addition, it’s experimental, making it even more unsafe. It’s definitely not a good option for React enterprise applications today but it’s worth keeping an eye on it.

MobX and Redux do not have any of these issues, and most big players on the market use them. What makes them different from each other are their respective learning curves. MobX requires a basic understanding of reactive programming. If the engineers involved in a project are not skilled enough, the application may end up with code inconsistencies, performance issues, and increased development time. MobX is acceptable and will meet your needs if your team is aware of reactivity.

Redux has some issues as well, mostly regarding scalability and performance. However, unlike MobX, there are proven solutions to these problems.

Taking every advantage and disadvantage into account, and considering my personal experience, I recommend Redux as the best option for React enterprise-level applications.


Further Reading on the Toptal Engineering Blog:

Understanding the basics

Yes, state management is necessary in React. In fact, it's crucial to use a proper state management library because any change in React state is immediately shown to the user

React’s useState is the best option for local state management. If you need a global state solution, the most popular ones are Redux, MobX, and the built-in Context API. Your choice will depend on the size of your project, your needs, and your engineers’ expertise.

Maintaining global state is easy if you keep it clean. It should contain only those items that are shared across multiple loosely connected components.

Context API’s functionality is tiny out of the box. It was not built and optimized for high-frequency updates but rather for low-frequency updates like theme updates and authentication management.

No, Redux doesn’t need a lot of boilerplate. With the introduction of the official Redux Toolkit, describing state management in Redux has become concise.

MobX is global state management implemented with RxJS. If you want to become an ace, learn RxJS. It’ll be helpful not only for understanding a single library, but also for the whole concept of reactivity.

Recoil performs well, even in its experimental stage. It's not ideal for a large application, but it would be useful for a tiny one that does not depend on the state too much.

Comments

B K
Dan Abramov did not create react, this is a common misconception. He is on the react core team though. Jordan Walke created react, and recently left facebook.
Simon Kaier
Thanks for the great article. There is a typo "Understanding the basics" -> "Which state management is the best in React?": "...the most popular ones are __React__, MobX, and built-in Context API." Not sure if that even belongs to the article though :D
Teimur Gasanov
Thank you very much for this notice! It'll be fixed soon.
Albert Gao
Great article, one thing about MobX, you DO NOT need to know RxJs in order to use MobX, in fact, the whole point of MobX is to enable reactive programming without learning that giant RxJs at all. Also, The current trend in community is Zustand and Jotai
Vanja
What if... this choice mostly didn't matter? Of course, with the understanding that global state is used only when it is global. Wouldn't Context suffice?
comments powered by Disqus