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
oryarn 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 vianpm
oryarn
. 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.
- You do not have to import or use the
- 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>
);
}
}
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.
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>
);
}
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
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.
This conceptual picture can be applied to the above example, or any other application using MobX.
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?
- Simple and un-opinionated
- Makes hooks the primary means of consuming state
- Doesn't wrap your app in context providers
- Can inform components transiently (without causing render)
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)
...
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.
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)
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
- https://www.jsdelivr.com/package/npm/effector
- https://cdn.jsdelivr.net/npm/effector/effector.cjs.js
- https://cdn.jsdelivr.net/npm/effector/effector.mjs
- https://cdn.jsdelivr.net/npm/effector-react/effector-react.cjs.js
- https://cdn.jsdelivr.net/npm/effector-vue/effector-vue.cjs.js