Simplifying State Management in React

Simplifying State Management in React

Introduction

In our Next.js project, we used the built-in React.Context for managing state. However, as our project grew, the simplicity we initially enjoyed became somewhat of a headache. To add even a simple feature to one component, we had to create a context, hooks, add a provider, and wrap the entire app with it. I mean...

Introduction

This article shares our adventure in testing different state management tools and picking the best one for us.

Initially, we needed to conduct research to discover our options.

Requirements

Before we started looking for a solution, we had to define what we were looking for. Here are the requirements we had, that can be applied to any other type of library we would choose (we used the same when we were looking for sliders-library):

After a quick investigation, we decided to compare MobX, Zustand, Jotai, Valtio, and Recoil. Those are the most popular libraries right now.

Investigation

MobX

MobX relies on the concept of observables and reactions. Observables are the state that can be observed, and reactions are the side effects that are triggered when the state changes.

Pros:

Cons:

Code example:

import { observable, action } from "mobx";
import { observer } from "mobx-react-lite";

const appState = observable({
  count: 0,
  increment: action(function () {
    this.count++;
  }),
});

const MyComponent = observer(() => {
  return (
    <div>
      Count: {appState.count}
      <button onClick={appState.increment}>Increment</button>
    </div>
  );
});

Zustand

Zustand is a small, fast, and scalable state management library with simplified flux principles.

Pros:

Cons:

Code example:

import create from "zustand";

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

const MyComponent = () => {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);

  return (
    <div>
      Count: {count}
      <button onClick={increment}>Increment</button>
    </div>
  );
};

Jotai

Jotai is a state management library that uses atoms to manage state. It has component-centric approach to state management in React applications. It focuses on breaking down the application state into smaller, independent pieces called atoms.

Pros:

Cons:

Code example:

import { atom, useAtom } from "jotai";

const countAtom = atom(0);

const MyComponent = () => {
  const [count, setCount] = useAtom(countAtom);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      Count: {count}
      <button onClick={increment}>Increment</button>
    </div>
  );
};

Valtio

Valtio is a state manager that uses the proxy pattern for managing state using the Proxy object. It provides a simple and efficient way to manage state in React applications.

Pros:

Cons:

Code example:

import { proxy } from "valtio";

const state = proxy({ count: 0 });

const MyComponent = () => {
  const count = state.count;

  const increment = () => {
    state.count++;
  };

  return (
    <div>
      Count: {count}
      <button onClick={increment}>Increment</button>
    </div>
  );
};

Recoil

Recoil is an experimental state management framework presented by Facebook. It is based on the concept of "atoms" (as Jotai). Recoil allows to create a data-flow graph that flows from atoms through selectors and down into React components. Atoms are units of state that components can subscribe to. Selectors transform this state either synchronously or asynchronously.

Pros:

Cons:

Code example:

import { atom, useRecoilState } from "recoil";

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

const MyComponent = () => {
  const [count, setCount] = useRecoilState(countState);

  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      Count: {count}
      <button onClick={increment}>Increment</button>
    </div>
  );
};

Comparison

To differentiate those libraries we read a couple of articles, compared basic features and looked at the community.

Libraries comparison
NPM downloads

Since Recoil and Mobx have significantly larger bundle sizes than the others, we decided to exclude them from the competition. We also excluded Valtio, because it has the smallest community and is not as popular as the others.

It left us with Zustand and Jotai. Both of them are relatively new, but have a lot of potential. They are lightweight, support TypeScript and are actively supported. So we decided to compare them in more details and to try each of them in our project.

Jotai is a great option and has some unique features, but it may have a steeper learning curve than Zustand due to its reliance on React hooks and more advanced features like atoms and derived state.

And Zustand is known for its simplicity and ease of use. It uses a functional programming approach which makes it easy to reason about and test, and it provides a straightforward API for creating and updating global state.

Zustand is not much older than Jotai but has a community that's twice as large..

Although it was mentioned that Jotai has more features, Zustand’s features are sufficient for our project. No need to choose a richer library just because it is richer.

Conclusion

Are we happy with Zustand? So far, yes. We re-wrote some existing contexts with Zustand and our code became cleaner. Did we get rid of React.Context? No, and we're not going to. We still use it for some cases, but Zustand is a great addition to our project.

Useful links

Yaroslava Mashnenko's Profile

Yaroslava Mashnenko

Software Engineer