A comprehensive overview of React State libraries
Tags: react, redux, mobx, hooks
Published: 2020-06-03 00:04:02

# 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.onrender.com/todos/ for REST API and https://todo-backend-graphql.onrender.com/graphql for GraphQL API. (The service goes to sleep when idle, so please allow some time after the initial request for the service to spin up.)
The code is on *codesandbox.io* so you can easily run it or fork it and edit.

# Table Of Contents
  * [Class components](#class-components)
  * [useState](#use-state)
  * [useThunkReducer](#use-thunk-reducer)
  * [Redux with redux-thunk](#redux-thunk)
  * [Redux with redux-saga](#redux-saga)
  * [MobX class component](#mobx-class)
  * [MobX and context (not null)](#mobx-context-not-null)
  * [MobX and context (null)](#mobx-context-null)
  * [MobX and useLocalStore](#mobx-local-store)
  * [React plain Context](#plain-context)
  * [Apollo Client](#apollo-client)
  * [React Query](#react-query)
  * [XState](#xstate)
  * [SWR](#swr)
  * [Zustand](#zustand)
  * [React Recoil](#react-recoil)
  * [MobX-state-tree](#mobx-state-tree)
  * [MobX-state-tree with flow function](#mobx-state-tree-flow)
  * [RxJS](#rxjs)
  * [Redux Toolkit](#redux-toolkit)
  * [localStorage API](#local-storage)
  * [IndexedDB API](#indexed-db)
  * [URL](#url)
  * [Immer](#immer)
  * [React Automata](#react-automata) TBD
  * [Unstated](#unstated) TBD
  * [Unstated Next](#unstated-next) TBD
  * [Relay](#relay) TBD
  * [React Async](#react-async) TBD
  * [Overmind](#overmind) TBD
  * [Akita](#akita) TBD
  * [Hookstate](#hookstate) TBD
  * [Jotai](#jotai) TBD
  * [Valtio](#valtio) TBD

## <a name="class-components"></a> State in class components
We create a class component and use its `state` property and `setState` method.
{% codesandbox v4zj4 view=split %}

## <a name="use-state"></a> useState hook
Earlier, when we wanted to manage the state, we had to use a class component. This is no longer the case with the arrival of hooks. This sandbox uses `useState` hook to manage the state.
{% codesandbox 4rxm5 view=split %}

## <a name="use-thunk-reducer"></a> useThunkReducer
This sandbox uses `useThunkReducer` from `react-hook-thunk-reducer`, which is similar to the built-in `useReducer`, but unlike `useReducer`, it allows us to dispatch async actions that are needed for fetching. This is an alternative to Redux and `redux-thunk`.
{% codesandbox wnrzdh view=split %}

## <a name="redux-thunk"></a> Redux with redux-thunk
This approach has proven to be the most verbose one.
Writing Redux with TypeScript results in even more boilerplate code.
Redux needs another helper library for side effects (fetching etc.) such as `redux-thunk` or `redux-saga`.
In this example `redux-thunk` is used.
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
{% codesandbox mp8rh view=split %}

## <a name="redux-saga"></a> Redux with redux-saga
Redux again, but this time using `redux-saga` instead of `redux-thunk`.
{% codesandbox pi77io view=split %}

## <a name="mobx-class"></a> 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`.
```React
<Provider {...store}>
  <TodoList/>
</Provider>
```
This approach is deprecated and the following ones taking advantage of React Context should be used.
{% codesandbox zgs31 view=split %}

## <a name="mobx-context-not-null"></a> MobX and context (not null)
Here we take advantage of custom `useStores` hook.
```React
const {TodoStore} = useStores();
```
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.
{% codesandbox urxdc view=split %}

## <a name="mobx-context-null"></a> 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
{% codesandbox lx3pb view=split %}

## <a name="mobx-local-store"></a> MobX and useLocalStore
We have seen `useLocalStore` hook used in the [MobX and context (null)](#mobx-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.
{% codesandbox mnk3s view=split %}

Functions like `observer` can be imported from [mobx-react-lite](https://github.com/mobxjs/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. 

## <a name="plain-context"></a> 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.
{% codesandbox irgwe view=split %}
Older solution using `Context.Consumer` render props component.
{% codesandbox ixnol view=split %}

## <a name="apollo-client"></a> Apollo Client
Here, we use [Apollo's](https://www.apollographql.com/docs/react/) `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.
{% codesandbox tqtsp view=split %}

## <a name="react-query"></a> React Query
`useQuery` and `useMutation` hooks with caching, optimistic updates and automatic refetching.
This and many more features are available with [React Query](https://github.com/tannerlinsley/react-query).
React Query works with Promise-based APIs.
The following sandbox demonstrates use with both REST API ([fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)) and GraphQL API ([graphql-request](https://github.com/prisma-labs/graphql-request) – a Promise-based GraphQL client).
{% codesandbox 7h6fg view=split %}

## <a name="xstate"></a> XState
Uses finite states machine to manage state.
[XState repository](https://github.com/davidkpiano/xstate).
{% codesandbox 61dnm view=split %}

## <a name="swr"></a> Vercel's SWR
SWR works with Promise-based APIs.
The following sandbox demonstrates use with both REST API ([fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)) and GraphQL API ([graphql-request](https://github.com/prisma-labs/graphql-request) – a Promise-based GraphQL client).
[SWR repository](https://github.com/vercel/swr).
{% codesandbox 6t7wf view=split %}

## <a name="zustand"></a> 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](https://github.com/react-spring/zustand).
{% codesandbox wh0qt view=split %}

## <a name="easy-peasy"></a> Easy Peasy
A redux-like library. Uses store, StoreProvider, dispatching of actions and thunks etc. It is compatible with Redux DevTools.
[Easy Peasy repository](https://easy-peasy.now.sh/)
{% codesandbox 0eork view=split %}

## <a name="react-recoil"></a> React Recoil
{% youtube _ISAA_Jt9kI %}
[Getting started](https://recoiljs.org/docs/introduction/getting-started)
{% codesandbox qi5ls view=split %}

## <a name="mobx-state-tree"></a> MobX-state-tree
[Getting started](https://mobx-state-tree.js.org/intro/getting-started)
{% codesandbox m2t3m view=split %}

## <a name="mobx-state-tree-flow"></a> 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](https://mobx-state-tree.js.org/concepts/async-actions).
The difference is in model's `fetchTodos` action.
{% codesandbox wstw4 view=split %}

## <a name="rxjs"></a> 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.
{% codesandbox q5fyj view=split %}
This [article about RxJS with React Hooks for state management](https://blog.logrocket.com/rxjs-with-react-hooks-for-state-management/) explains this concept really nicely.

## <a name="redux-toolkit"></a> 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.
{% codesandbox 2iitx view=split %}
Same functionality, but taking advantage of Redux Toolkit's `createAsyncThunk` function:
{% codesandbox en7vn view=split %}
Read more about [Redux Toolkit](https://redux-toolkit.js.org/).

## <a name="local-storage"></a> localStorage API
By utilizing `localStorage API`, we fetch todos from the endpoint only once. Subsequent page refreshes load todos from the `localStorage` unless the `localStorage` entry is deleted. The downside is that if someone else changes the todos in the database behind the endpoint, we'll still see the old data.
{% codesandbox 7fvljk view=split %}
Read more about [localStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

## <a name="indexed-db"></a> IndexedDB API
This approach is functionally similar to the `localStorage API`. We fetch todos from the endpoint only once. Subsequent page refreshes load todos from the `IndexedDB` unless the database is deleted. Again, the downside is that if someone else changes the todos in the database behind the endpoint, we'll still see the old data.
Unlike `localStorage`, which is useful for storing smaller amounts of data, `IndexedDB` is a low-level API for client-side storage of significant amounts of structured data.
{% codesandbox 07c6iy view=split %}
Read more about [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).

## <a name="url"></a> URL
In this example we use [`useUrlState` hook](https://www.npmjs.com/package/@ahooksjs/use-url-state) to store the state in the URL. When there are no URL parameters, the todos are fetched from the endpoint and then reflected in the URL. The URL parameters are the source of truth – sharing the URL shows the same todos to whoever opens the link.
{% codesandbox x3hf6x view=split %}

## <a name="immmer"></a> Immmer
Whereas `useState` hook assumes any state that is stored inside it is treated as immutable – we write `setState(newState)` – Immer allows us to write `setState(produce(oldState => { mutate oldState }))`.
We can further simplify this by using `useImmer` hook from the `use-immer` package instead of the `useState` hook which allows us to omit the `produce` function: `setState(oldState => { mutate oldState })`.
This sandbox contains both approaches.
{% codesandbox mgxmf0 view=split %}


# 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](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367).

# Resources:
[Mobx docs](https://mobx-react.js.org/)
[React Redux docs](https://react-redux.js.org/introduction/quick-start)
[React docs](https://reactjs.org/docs/getting-started.html)
Cover photo by [v2osk](https://unsplash.com/@v2osk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText).