DEV Community

Cover image for A comprehensive overview of React State libraries
Petr Janik
Petr Janik

Posted on • Updated on

A comprehensive overview of React State libraries

Background

I have started learning React two months ago. At that time, I was overwhelmed by all the different possibilities of React. I have addressed two of them already in the previous aticles of this series. The remaining one - state management - will be discussed in this article.
Throughout my learning, these examples have proven priceless to me as I always had some React sandbox to play and experiment with. They served as a reminder of what I already learnt. I hope they will be of use to you as well.

How this works

I have implemented a simple TODO app in every state management way I came across. The functionality is pretty basic. You can list existing todos and add a new todo.
It uses a backend running on heroku. https://todo-backend-rest.herokuapp.com/todos/ for REST API and https://todo-backend-graphql.herokuapp.com/ for GraphQL API.
The code is on codesandbox.io so you can easily run it or fork it and edit.

Table Of Contents

State in class components

We create a class component and use its state property and setState method.

useState hook + useThunkReducer

Earlier, when we wanted to manage state, we had to use a class component.
This is no longer the case with the arrival of hooks.
This sandbox contains two approaches. The first one is useState hook and the second one would be useReducer. However, I had to use useThunkReducer from react-hook-thunk-reducer instead of built-in useReducer to dispatch async actions that are needed for fetching. This is an alternative of Redux and redux-thunk.

Redux

This approach has proven to be the most verbose one.
Witing Redux with typescript's type checking is even more boilerplate code.
Redux needs another helper library for side effects (fetching etc.) such as redux-thunk or redux-saga.
This sandbox contains:

  • an older approach using mapStateToProps and mapDispatchToProps connected with react-redux connect HOC function
  • a newer approach using useSelector and useDispatch hooks

MobX class component

Mobx is used for state management (both local and global) and for observing.
This sandbox contains:

  • an older approach using class stores and @inject and @observer annotations.
  • class component using inject and observer HOC
  • functional component using inject and observer HOC The store is provided via Provider component from mobx-react.
<Provider {...store}>
  <TodoList/>
</Provider>
Enter fullscreen mode Exit fullscreen mode

This approach is deprecated and the following ones taking advantage of React Context should be used.

MobX and context (not null)

Here we take advantage of custom useStores hook.

const {TodoStore} = useStores();
Enter fullscreen mode Exit fullscreen mode

The useStores hook consumes storesContext via useContext hook.
storesContext is initialized to { TodoStore: new TodoStore() } so we do not need to provide the context in <storesContext.Provider> component.

MobX and context (null)

If we didn't want to create context with initial value as in previous approach, we could create a custom <StoreProvider> component. This component returns a <storesContext.Provider>.
The useStores hook now also checks, whether the store (i. e. the value of context) is not null.
This sandbox also contains 4 ways of observing the state:

  • observer HOC with regular function
  • observer HOC with arrow function
  • <Observer> component
  • useObserver hook

MobX and useLocalStore

We have seen useLocalStore hook used in the MobX and context (null).
From the MobX documentation:

The naming useLocalStore was chosen to indicate that store is created locally in the component. However, that doesn't mean you cannot pass such store around the component tree. In fact it's totally possible to tackle global state management with useLocalStore despite the naming. You can for example setup bunch of local stores, assemble them in one root object and pass it around the app with a help of the React Context.

Which is exacty what we did in the previous example.
In this example, however, we insert the code of the store directly into the component.

Functions like observer can be imported from mobx-react-lite, which is a lighter version of mobx-react. It supports only functional components and as such makes the library slightly faster and smaller. Note, however, that it is possible to use <Observer> inside the render of class components. Unlike mobx-react, it doesn't support Provider/inject, as useContext can be used instead.

React plain Context

We can create a global state in App component and then pass it to other components by using React Context.
Modern solution using useContext hook.

Older solution using Context.Consumer render props component.

Apollo Client

Here, we use Apollo's useQuery and useMutation hooks.
Previously, we had to use apollo-link-state to manage state with Apollo. As of Apollo Client 2.5, local state handling is baked into the core, which means it is no longer necessary to use apollo-link-state.

React Query

useQuery and useMutation hooks with caching, optimistic updates and automatic refetching.
This and many more features are available with React Query.
React Query works with Promise-based APIs.
The following sandbox demonstrates use with both REST API (fetch) and GraphQL API (graphql-request – a Promise-based GraphQL client).

XState

Uses finite states machine to manage state.
XState repository.

Vercel's SWR

SWR works with Promise-based APIs.
The following sandbox demonstrates use with both REST API (fetch) and GraphQL API (graphql-request – a Promise-based GraphQL client).
SWR repository.

Zustand

As their README says:

Small, fast and scaleable bearbones state-management solution. Has a comfy api based on hooks, that isn't boilerplatey or opinionated, but still just enough to be explicit and flux-like.

Zustand repository.

Easy Peasy

A redux-like library. Uses store, StoreProvider, dispatching of actions and thunks etc. It is compatible with Redux DevTools.
Easy Peasy repository

React Recoil


Getting started

MobX-state-tree

Getting started

MobX-state-tree with flow function

flow function is a suggested way to handle asynchronous actions. There are multiple advantages to it, including direct modification of its own instance. Also the onAction middleware will only record starting asynchronous flows, but not any async steps that are taken during the flow.
Read more about flow in documentation.
The difference is in model's fetchTodos action.

RxJS

In this example I used a common global store with RxJS Subject to which individual components can subscribe their setState function. Changes are dispatched by calling functions on the store.

This article about RxJS with React Hooks for state management explains this concept really nicely.

Redux Toolkit

A less-boilerplatey Redux. Personally, I have really enjoyed this one. It is compatible with Redux code you have been using so far.

Same functionality, but taking advantage of Redux Toolkit's createAsyncThunk function:

Read more about Redux Toolkit.

So which one should you choose?

First thing to note is that those ways are not mutually exclusive, you can make use of both Redux and Apollo Client at the same time.
I'd say that Redux is a lot of fun and provides a nice way of debugging when using redux-devtools-extension. However, the code overhead is huge, especially when combined with TypeScript. For smaller projects, I would choose MobX-state-tree instead or even plain React Context with hooks for smaller applications.

This article (from 2016) discusses the advantages and drawbacks of Redux.

Resources:

Mobx docs
React Redux docs
React docs
Cover photo by v2osk on Unsplash.

Discussion (2)

Collapse
jackedwardlyons profile image
Jack Lyons

Great post! You should think about adding Overmind to the list :)
overmindjs.org/views/react

Collapse
petr7555 profile image
Petr Janik Author

Thank you for the suggestion. I will definitely add that one!