State Management in React — Overview

Fabio Brunori
Valtech Switzerland
7 min readJan 31, 2023

Table of content

Introduction

I found myself in many different project over the past few years (here’s consultancy for you) and all of them had something in common: data.

Being it in the form of tables, social entities, charts or anything that you could come across while surfing the web you’ll find some kind of data and some way to interact with it.

As developers we’re constantly looking for ways to be more efficient in handling these interactions so we can rest without the need of taking some medicine for the headache all this reasoning and debugging is causing us.

Plenty of solutions popped up in recent years as we managed to make our code more and more re-usable and abstracted; we migrated from asking ourselves ‘how do I handle this scenario?’ to ‘which library do I use so that whatever will come in the project will have me covered?’.

So many options came out that our main problem now is to figure it out what library is best, often forgetting about the very problems we were out to solve.

This is especially true when we specifically talk about React and managing state for it. There has been quite the evolution in State Management as React itself doesn’t give much to work with in the first place.

One library has some drawbacks that another one tries to solve (looking at you, boilerplate code), one is too slow, one is fast but missing some functionality, etc.. so many to know and just 24 hours in a day.

Since solutions that are omnicomprehensive do exists, why then don’t we just choose one and stick with it? I’ve seen companies trying to adopt one solution for all their projects and none of them succeeded in doing so. There’s just so much difference between each situation and so many approaches that it’s practically impossible to have a holy grail of stack that’ll always be the best solution.

To make an analogy, a race car will do the job as transporting a cargo (probably more trips will be needed) but it’s easy to figure how a trailer would be most effective. On the other hand, if your goal is just to reach your destination quickly a sport car is undoubtadly a more reasonable choice.

Clearly the differences in the software world tend to smoothen a little, computers and phones are fast enough that unless particular situations, most solutions will feel the same to the end user. Does this mean that a single swiss-knife option is preferrable as you don’t need to learn new stuff anymore? I met some people that have this view and IMO there’s nothing wrong with it as it makes the job done. I’m curious by nature though so I’m inclined to try and figure out what’s out there and experiment a bit; especially when something catches my interest.

For this reason I started asking myself what’s the deal with State Management in React, what’s for and what problem does it solve. Do we actually need some fancy library to help us structure our code or the tools provided by react are enough to comfortably doing the job?

To answer this I started from the very basics and worked my way up implementing solutions to the same problem but with different State Management libraries.

What’s State?

Big dumb questions first, what we are actually talking about?

State is any information remembered so the app/system can work.

State can be local, when it resides in the component that consumes it or shared when the same instance is consumed in multiple components.

State can reside on the server (crud applications) or in the client (Web editors).

Local state is generically a good thing; If an information can live and die where it’s used it makes easy to understand what it is for, if it’s possible to clean it up or refactor it.

Shared state can be either stored Globally or in the closest common ancestor (putting it elsewhere feels like spaghetti code). Both solutions have their strong points and weakness:

Advantages of Global State

  • Always available (it stays there for the whole session and can be used in every component
  • No need to think about where to put some new information that needs to be stored
  • Less refactoring needed (no lifting up an down when new features are implemented)
  • It can be easily stored // initialized from cold.

Disadvantages of Global State

  • It stays there for the whole session (especially if immutable, this might significantly slow the performances)
  • Can be confusing (why is this data here? can I use it for my new feature? which components are depending on this? …)
  • Doesn’t scale up very well (as the application loads more data, if the part that is not needed anymore isn’t cleaned up performances will be affected).
  • It makes naming things hard (everything needs to include its scope in the name)

Advantages of Shared State

  • It’s easier to name things (locality gives plenty of context)
  • It’s safer to delete and refactor (usages are very close thus easy to follow)
  • It’s more maintainable (it’s easier to understand what’s there for if it stays close to its usages).
  • scales up pretty well (unmounting components will clean up the state)

Disadvantages of Shared State

  • Code in components might get significantly longer (mitigable, eg. moving logic into a hook)
  • Using similar local names in different components might result confusing (search and replace with non-related hits)
  • Can require a lot of refactoring (lifting up and down is a pain)
  • Can cause Prop Drilling

As this list it’s probably not all there is, feel free to add points in the comments.

How To Share state?

We agree that not sharing state that doesn’t need to be shared is not a bad approach by any means, but what about the information that we have to share?

State Lifting:

  • Keep the state as local as possible
  • Direct tracking
  • Prop drilling

Context:

  • Avoids prop drilling
  • Careful with providers
  • Can be global
  • Efficient rerenders (only components that consumes the context gets rendered when its state changes), sort of
  • Hard to track // maintain (who’s changing something and causing this render cycle?)

External Libraries

  • Hide complexity
  • Can be framework agnostic (good or bad?)
  • Could be react specific (leveraging React apis for integration and//or performances)

External Entities (manual implementation working with non-react apis like localstorage, or url, …)

  • Sync between multiple tabs or windows
  • Share state sending link (ex. filtered searches)

State Management Patterns and Libraries:

list might not be completed, new stuff comes out all the time and I might not have heard of it yet.

for client State:

Redux

  • Predictable
  • Easy to track why and when and how the state was updated
  • Centralized
  • Boilerplate
  • Agnostic

MobX

  • Minimalistic (no boilerplate)
  • Reactive
  • Async with no effort needed
  • Proxy-based
  • Agnostic

Recoil

  • Simple apis?
  • Async out of the box
  • Minimal (no boilerplate)
  • React-api-friendly

Jotai

  • Simplified version of recoil (4x smaller)
  • Somewhat limited (no snapshots, no React Fast Refresh)
  • Stable persistent state apis
  • Differences with Recoil

Zustand

  • Async out of the box
  • Centralized

Valtio

  • Proxy-based
  • State can be directly modified (Anti-pattern?)
  • Integrated es-lint plugin
  • Agnostic

RxJS

  • Boilerplate (react bindings)
  • Agnostic
  • Streams as state
  • Lots of theory to be understood

Rematch

  • Redux implementation
  • Boilerplate to the minimum (but still there)
  • Very small footprint
  • Complex typings (require type casting for non-trivial structures)

Hookstate

  • Getters and setters, Java vibe
  • Names of fields in the states could conflict with the APIs (ex. myState.value needs to be accessed as myState.nested.value.get() )
  • State structure is very easy to follow and short to write
  • Proxy magic but more explicit

A̵k̵i̵t̵a̵ Elf

  • Uses rxjs
  • Entity-based stores
  • Comes with a cli
  • Some boilerplate (defining repositories)
  • Database (CRM) — like operations out of the box
  • Agnostic

Note:

xState is not in the list as it doesn’t help sharing state between components but only to handle complex behaviours.

Now that we know a few libraries and some of their perks what’s next? how do we choose the right one? which one is the best?

I believe that a generic best doesn’t exist and the question should be formulated as ‘which one is the best for what?’ instead. Even this question may or may not have an universal and scientific answer, so I decided to find some practical problems that I might face while developing web applications and implement solutions in every library listed above, noting down things I liked and things I didn’t.

Each library seems to have certain strong points; therefore every situation I’m tackling tries to highlight a different category of problem.

Since this matter is complex and requires a lot of talking, I’ll be structuring this topic as a series of articles each describing solutions for every library to a single issue. I’ll post updates to this introduction with links to every follow-up.

I may also reserve the freedom of adding new interesting scenarios to cover as much ground as possible.

There will be a conclusion article that will try to make up a technical summary that I’ll be using as reference for future projects.

Problems Table

Local State Editor:

  • User can add squares (1 or 1000 at a time)
  • Drag squares around in a board

Shows how well the library can scale with a lot of interactive data being displayed at the same time and edited.

Undo-Redo Board (Link TBD):

  • Grid of squares
  • Clicking on a square randomizes its background color.
  • Undo and Redo buttons

Highlights how easy it is to manage state history.

More problems to come, comment what you’d like to see here and stay tuned!

--

--