Best ReactJS State Management Libraries

A curated list of the best State Management libraries.

1. reactn

ReactN is an extension of React that includes global state management.

It treats global state as if it were built into React itself -- without the boilerplate
of third party libraries.

Install

  • npm install reactn or
  • yarn add reactn

Features

No boilerplate

For function components, import { useGlobal } from "reactn"; to harness the
power of React Hooks!

For class components, simply change import React from "react"; to
import React from "reactn";, and your React class components will have global
state built in!

If you prefer class decorators, you can continue to
import React from "react"; for your components and additionally
import reactn from "reactn"; for access to the code>@reactn</code decorator!

Intuitive

Function components

Global state in function components behaves almost identically to local state.

You use [ global, setGlobal ] = useGlobal() to access the entire global state
object.

You use [ value, setValue ] = useGlobal(property) where property is the
property of the global state you want to get and set.

Global reducers in function components behaves almost identically to local
reducers.

You use dispatch = useDispatch(reducerFunction) to mimic the behavior of
useReducer, where instead of providing an initial state, the state of the
reducer is the ReactN global state object.

You use dispatch = useDispatch(reducerName) to use a reducer that was added
by the addReducer helper function.

You use dispatch = useDispatch(reducerFunction, property) or
[ value, dispatch ] = useDispatch(reducerFunction, property) to apply a
reducer specifically to a global state property. This is very similar to
React's native useReducer functionality.

Class components

Global state in class components behaves exactly like local state!

You use this.global and this.setGlobal to get and set the global state.

You use this.dispatch.reducerName() to dispatch to a reducer that was added
by the addReducer helper function.

The code>@reactn</code decorator allows you to convert classes that extend
React.Component to ReactN global state components.

Map state to props

If you prefer Redux's connect functionality, pure functions, or are dealing
with deeply nested objects, a
withGlobal higher-order component is also available.

Getting started

Managing multiple states

This README is for managing a single global state. This is ideal for most
applications. If you are using concurrent server-side rendering or otherwise
want to work with multiple global states, follow the README for the
Provider
component, which allows you to limit a ReactN state to a React Context.

If you are unsure whether or not you need multiple global states, then you do
not need multiple global states.

Initializing your state

You can initialize your global state using the setGlobal helper function. In
most cases, you do not want to initialize your global state in a component
lifecycle method, as the global state should exist before your components
attempt to render.

It is recommended that you initialize the global state just prior to mounting
with ReactDOM.

import React, { setGlobal } from 'reactn';
import ReactDOM from 'react-dom';
import App from './App';

// Set an initial global state directly:
setGlobal({
  cards: [],
  disabled: false,
  initial: 'values',
  x: 1,
});

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

TypeScript support

ReactN supports TypeScript out of the box! It is written entirely in TypeScript.
This gives it powerful intellisense, auto-complete, and error-catching abilities.

TypeScript can maintain inferred global state and reducer shape of a
Providers.
Unfortunately, without your help, it cannot track the shape of the "default"
global state -- the one manipulated by the setGlobal and addReducer helper
functions.

In order to tell TypeScript the shape of your global state when you are not using
a Provider, create a file at src/global.d.ts with the following contents:

import "reactn";

declare module "reactn/default" {

  export interface Reducers {

    append: (
      global: State,
      dispatch: Dispatch,
      ...strings: any[]
    ) => Pick<State, "value">;

    increment: (
      global: State,
      dispatch: Dispatch,
      i: number,
    ) => Pick<State, "count">;

    doNothing: (
      global: State,
      dispatch: Dispatch,
    ) => null;
  }

  export interface State {
    count: number;
    value: string;
  }
}

In the above file, we extend the Reducers and State interfaces in the
"reactn/default" file. While you will never use "reactn/default" in
your code, ReactN will use it to determine the shape of the default
global state.

The above example will add append, increment, and doNothing to your
useDispatch and this.dispatch auto-completion and typing. The parameters
and return values will also be correctly typed. In addition, it will also
add count and value to your useGlobal and this.global auto-competion
with the appropriate types as well.

Developer tools

ReactN is compatible with the
Redux DevTools extension.

  • Install the Redux DevTools extension to your browser or environment.
  • Install the redux package to your project via npm or yarn. This is used
    to create a middleware Redux store for the Redux DevTools extension.
    • You do not have to import or use the redux package anywhere in your
      project.
    • You do not need to create a Redux store, reducer, or actions.
    • redux is just a peer dependency. It will be managed automatically.
  • Follow the instructions on the
    ReactN DevTools README.

Examples

Class components

By importing React from reactn instead of react, you bake global state
directly into the React namespace. As a result, Component and PureComponent
will have access to the global and dispatch member variables and
setGlobal method.

import React from 'reactn'; // <-- reactn
import Card from '../card/card';

// Render all cards in the global state.
export default class Cards extends React.PureComponent {
  componentDidMount() {
    // Hydrate the global state with the response from /api/cards.
    this.setGlobal(
      // Despite fetch returning a Promise, ReactN can handle it.
      fetch('/api/cards')
        .then((response) => response.json())

        // Set the global cards property to the response.
        .then((cards) => ({ cards }))

        // Fail gracefully, set the global error
        //   property to the caught error.
        .catch((err) => ({ error: err }))
    );
  }

  render() {
    // For each card in the global state, render a Card component.
    // this.global returns the global state,
    //   much the same way this.state returns the local state.
    return (
      <div>
        {this.global.cards.map((card) => (
          <Card key={card.id} {...card} />
        ))}
      </div>
    );
  }
}

Read more


2. hookstate

The most straightforward, extensible and incredibly fast state management that is based on React state hook.

Hookstate is a modern alternative to Redux, Mobx, Recoil, etc. It is simple to learn, easy to use, extensible, very flexible and capable to address all state management needs of large scalable applications. It has got impressive performance and predictable behavior.

Read more


3. easy-peasy

Vegetarian friendly state for React.

Easy Peasy is an abstraction of Redux, providing a reimagined API that focuses on developer experience.

It allows you to quickly and easily manage your state, whilst leveraging the strong architectural guarantees and extensive eco-system that Redux has to offer.

  • Zero configuration
  • No boilerplate
  • React hooks based API
  • Extensive TypeScript support
  • Encapsulate data fetching
  • Computed properties
  • Reactive actions
  • Redux middleware support
  • State persistence
  • Redux Dev Tools
  • Global, context, or local stores
  • Built-in testing utils
  • React Native supported
  • Hot reloading supported

All of this comes via a single dependency install.

npm install easy-peasy

Fly like an eagle

Create your store

const store = createStore({
  todos: {
    items: ['Create store', 'Wrap application', 'Use store'],
    add: action((state, payload) => {
      state.items.push(payload);
    }),
  },
});

Wrap your application

function App() {
  return (
    <StoreProvider store={store}>
      <TodoList />
    </StoreProvider>
  );
}

Use the store

function TodoList() {
  const todos = useStoreState((state) => state.todos.items);
  const add = useStoreActions((actions) => actions.todos.add);
  return (
    <div>
      {todos.map((todo, idx) => (
        <div key={idx}>{todo}</div>
      ))}
      <AddTodo onAdd={add} />
    </div>
  );
}

Read more


4. Recoil

Recoil is an experimental state management framework for React.

Installation

The Recoil package lives in npm. Please see the installation guide

To install the latest stable version, run the following command:

npm install recoil

Or if you're using yarn:

yarn add recoil

Or if you're using bower:

bower install --save recoil

Read more


5. mobx

MobX is a Simple, scalable state management.

Anything that can be derived from the application state, should be. Automatically.

MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming.
The philosophy behind MobX is simple:

😙

Straightforward

Write minimalistic, boilerplate-free code that captures your intent.
Trying to update a record field? Simply use a normal JavaScript assignment —
the reactivity system will detect all your changes and propagate them out to where they are being used.
No special tools are required when updating data in an asynchronous process.

🚅

Effortless optimal rendering

All changes to and uses of your data are tracked at runtime, building a dependency tree that captures all relations between state and output.
This guarantees that computations that depend on your state, like React components, run only when strictly needed.
There is no need to manually optimize components with error-prone and sub-optimal techniques like memoization and selectors.

🤹🏻‍♂️

Architectural freedom

MobX is unopinionated and allows you to manage your application state outside of any UI framework.
This makes your code decoupled, portable, and above all, easily testable.

A quick example

So what does code that uses MobX look like?

import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"

// Model the application state.
class Timer {
    secondsPassed = 0

    constructor() {
        makeAutoObservable(this)
    }

    increase() {
        this.secondsPassed += 1
    }

    reset() {
        this.secondsPassed = 0
    }
}

const myTimer = new Timer()

// Build a "user interface" that uses the observable state.
const TimerView = observer(({ timer }) => (
    <button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))

ReactDOM.render(<TimerView timer={myTimer} />, document.body)

// Update the 'Seconds passed: X' text every second.
setInterval(() => {
    myTimer.increase()
}, 1000)

The observer wrapper around the TimerView React component will automatically detect that rendering
depends on the timer.secondsPassed observable, even though this relationship is not explicitly defined. The reactivity system will take care of re-rendering the component when precisely that field is updated in the future.

Every event (onClick / setInterval) invokes an action (myTimer.increase / myTimer.reset) that updates observable state (myTimer.secondsPassed).
Changes in the observable state are propagated precisely to all computations and side effects (TimerView) that depend on the changes being made.

MobX unidirectional flow

This conceptual picture can be applied to the above example, or any other application using MobX.

Read more


6. zustand

A small, fast and scalable bearbones state-management solution using simplified flux principles. Has a comfy api based on hooks, isn't boilerplatey or opinionated.

Don't disregard it because it's cute. It has quite the claws, lots of time was spent to deal with common pitfalls, like the dreaded zombie child problem, react concurrency, and context loss between mixed renderers. It may be the one state-manager in the React space that gets all of these right.

You can try a live demo here.

npm install zustand # or yarn add zustand

:warning: This readme is written for JavaScript users. If you are a TypeScript user, don't miss TypeScript Usage.

First create a store

Your store is a hook! You can put anything in it: primitives, objects, functions. State has to be updated immutably and the set function merges state to help it.

import create from 'zustand'

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

Then bind your components, and that's it!

Use the hook anywhere, no providers needed. Select your state and the component will re-render on changes.

function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}

function Controls() {
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

Why zustand over redux?

Why zustand over context?

  • Less boilerplate
  • Renders components only on changes
  • Centralized, action-based state management

Recipes

Fetching everything

You can, but bear in mind that it will cause the component to update on every state change!

const state = useBearStore()

Selecting multiple state slices

It detects changes with strict-equality (old === new) by default, this is efficient for atomic state picks.

const nuts = useBearStore((state) => state.nuts)
const honey = useBearStore((state) => state.honey)

If you want to construct a single object with multiple state-picks inside, similar to redux's mapStateToProps, you can tell zustand that you want the object to be diffed shallowly by passing the shallow equality function.

import shallow from 'zustand/shallow'

// Object pick, re-renders the component when either state.nuts or state.honey change
const { nuts, honey } = useBearStore(
  (state) => ({ nuts: state.nuts, honey: state.honey }),
  shallow
)

// Array pick, re-renders the component when either state.nuts or state.honey change
const [nuts, honey] = useBearStore(
  (state) => [state.nuts, state.honey],
  shallow
)

// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore((state) => Object.keys(state.treats), shallow)

For more control over re-rendering, you may provide any custom equality function.

const treats = useBearStore(
  (state) => state.treats,
  (oldTreats, newTreats) => compare(oldTreats, newTreats)
)

Overwriting state

The set function has a second argument, false by default. Instead of merging, it will replace the state model. Be careful not to wipe out parts you rely on, like actions.

import omit from 'lodash-es/omit'

const useFishStore = create((set) => ({
  salmon: 1,
  tuna: 2,
  deleteEverything: () => set({}, true), // clears the entire store, actions included
  deleteTuna: () => set((state) => omit(state, ['tuna']), true),
}))

Async actions

Just call set when you're ready, zustand doesn't care if your actions are async or not.

const useFishStore = create((set) => ({
  fishies: {},
  fetch: async (pond) => {
    const response = await fetch(pond)
    set({ fishies: await response.json() })
  },
}))

Read from state in actions

set allows fn-updates set(state => result), but you still have access to state outside of it through get.

const useSoundStore = create((set, get) => ({
  sound: "grunt",
  action: () => {
    const sound = get().sound
    // ...
  }
})

Reading/writing state and reacting to changes outside of components

Sometimes you need to access state in a non-reactive way, or act upon the store. For these cases the resulting hook has utility functions attached to its prototype.

const useDogStore = create(() => ({ paw: true, snout: true, fur: true }))

// Getting non-reactive fresh state
const paw = useDogStore.getState().paw
// Listening to all changes, fires synchronously on every change
const unsub1 = useDogStore.subscribe(console.log)
// Updating state, will trigger listeners
useDogStore.setState({ paw: false })
// Unsubscribe listeners
unsub1()
// Destroying the store (removing all listeners)
useDogStore.destroy()

// You can of course use the hook as you always would
const Component = () => {
  const paw = useDogStore((state) => state.paw)
  ...

Read more


7. react-redux

Official React bindings for Redux.
Performant and flexible.

Installation

Using Create React App

The recommended way to start new apps with React Redux is by using the official Redux+JS/TS templates for Create React App, which takes advantage of Redux Toolkit.

# JS
npx create-react-app my-app --template redux

# TS
npx create-react-app my-app --template redux-typescript

An Existing React App

React Redux 8.0 requires React 16.8.3 or later (or React Native 0.59 or later).

To use React Redux with your React app, install it as a dependency:

# If you use npm:
npm install react-redux

# Or if you use Yarn:
yarn add react-redux

You'll also need to install Redux and set up a Redux store in your app.

This assumes that you’re using npm package manager
with a module bundler like Webpack or
Browserify to consume CommonJS
modules
.

If you don’t yet use npm or a modern module bundler, and would rather prefer a single-file UMD build that makes ReactRedux available as a global object, you can grab a pre-built version from cdnjs. We don’t recommend this approach for any serious application, as most of the libraries complementary to Redux are only available on npm.

Read more


8. react-query

Hooks for fetching, caching and updating asynchronous data in React.

Here are its main Features:

  • Transport/protocol/backend agnostic data fetching (REST, GraphQL, promises, whatever!)
  • Auto Caching + Refetching (stale-while-revalidate, Window Refocus, Polling/Realtime)
  • Parallel + Dependent Queries
  • Mutations + Reactive Query Refetching
  • Multi-layer Cache + Automatic Garbage Collection
  • Paginated + Cursor-based Queries
  • Load-More + Infinite Scroll Queries w/ Scroll Recovery
  • Request Cancellation
  • React Suspense + Fetch-As-You-Render Query Prefetching
  • Dedicated Devtools


  • (depending on features imported)

Read more


9. effector

Business logic with ease

Effector implements business logic with ease for Javascript apps (React/React Native/Vue/Svelte/Node.js/Vanilla), allows you to manage data flow in complex applications. Effector provides best TypeScript support out of the box.

Effector follows five basic principles:

  • Application stores should be as light as possible - the idea of adding a store for specific needs should not be frightening or damaging to the developer.
  • Application stores should be freely combined - data that the application needs can be statically distributed, showing how it will be converted in runtime.
  • Autonomy from controversial concepts - no decorators, no need to use classes or proxies - this is not required to control the state of the application and therefore the api library uses only functions and plain js objects
  • Predictability and clarity of API - a small number of basic principles are reused in different cases, reducing the user's workload and increasing recognition. For example, if you know how .watch works for events, you already know how .watch works for stores.
  • The application is built from simple elements - space and way to take any required business logic out of the view, maximizing the simplicity of the components.

Installation

npm add effector

React

To getting started read our article how to write React and Typescript application.

npm add effector effector-react

Vue

npm add effector effector-vue

Svelte

Svelte works with effector out of the box, no additional packages needed. See word chain game application written with svelte and effector.

CDN

Read more


Related Posts