A good state management solution is foundational to building a frontend application with any amount of complexity beyond “Hello World!”. One of the most well-known state management libraries for React is Redux, but there are many alternatives that may suit you better, depending on your needs and taste.
Here are 5 popular alternatives to Redux in the React universe that offer user-friendly APIs and have been hardened with real-world usage. Importantly, they are also actively maintained, with all having an update in the last month (as of this writing in April 2024).
For consistency and ease of understanding, I’ve demonstrated each library's capabilities using a simple counter example. Let’s go!
Zustand
https://github.com/pmndrs/zustand
Github stars: 42k
Last updated: March 2024
Supports TypeScript: yes
Zustand keeps things simple and straightforward for managing state in React. You can accomplish most basic tasks by using just one export, create
, which exposes your store as a hook.
import { create } from 'zustand';
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<button onClick={decrement}>-</button>
{count}
<button onClick={increment}>+</button>
</div>
);
}
Use Zustand when…
- You need to build rapidly. Thanks to its simplicity and minimal setup, Zustand is perfect for rapid prototyping. Developers can quickly implement state management and make adjustments on the fly without getting bogged down by complex configurations.
- You need a flexible architecture. Zustand does not enforce a strict architecture, allowing developers to structure their state however they see fit. This flexibility is beneficial in projects that require a unique or unconventional state management setup.
Jotai
https://github.com/pmndrs/jotai
Github stars: 17k
Last updated: March 2024
Supports TypeScript: yes
Jotai provides a simple and flexible state management solution, using “atoms” for managing global state that's both easy to use and scalable. Atoms represent pieces of state that you can compose to achieve the desired complexity.
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
<button onClick={() => setCount(count - 1)}>-</button>
{count}
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
Use Jotai when…
- You can naturally divide your state. Jotai's atom-based approach is great for projects that benefit from fine-grained control over state. Each atom can be independently updated and composed, making it an excellent choice for applications where state needs to be managed in small, reusable pieces.
- Your project emphasizes immutability. Jotai’s API encourages the use of pure functions and immutable data, aligning with modern React development practices.
MobX
https://github.com/mobxjs/mobx
Github stars: 27k
Last updated: April 2024
Supports TypeScript: yes
MobX offers fine-grained observability and automatic tracking of state changes for React components using Functional Reactive Programming (FRP).
FRP (a comprehensive topic by itself!) allows you to declaratively specify your state and its changes over time. Using FRP, MobX tries to make state management as transparent and predictable as possible. You can read more about the principles of MobX here.
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';
// Class-based store
class CounterStore {
count: number = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
}
const counterStore = new CounterStore();
const Counter = observer(() => (
<div>
<button onClick={() => counterStore.decrement()}>-</button>
{counterStore.count}
<button onClick={() => counterStore.increment()}>+</button>
</div>
));
You can also create the store with a functional paradigm:
// Function-based store
function createCounterStore() {
return makeAutoObservable({
count: 0,
increment() {
this.count += 1;
},
decrement() {
this.count -= 1;
}
});
};
const counterStore = createCounterStore();
Use MobX when…
- You like object-oriented patterns. MobX’s API is designed to work seamlessly with classes (such as ES6 React classes) and objects, making it a natural fit for applications that use OOP principles extensively.
- Your project requires fine-grained reactivity. MobX gives you the ability to precisely control when your components re-render in response to state changes. You can ensure that only components that depend on changed observables are re-rendered, optimizing performance.
XState
https://github.com/statelyai/xstate
Github stars: 26k
Last updated: March 2024
Supports TypeScript: yes
XState leverages state machines and statecharts for managing state, which is especially useful for complex state logic and workflows. The initial state is set with the initial
key, and allowed state transitions are set within the on
key of each state within states
.
import { useMachine } from '@xstate/react';
import { createMachine, assign } from 'xstate';
const counterMachine = createMachine({
initial: 'active',
context: { count: 0 },
states: {
active: {
on: {
INCREMENT: { actions: assign({ count: ctx => ctx.count + 1 }) },
DECREMENT: { actions: assign({ count: ctx => ctx.count - 1 }) },
}
}
}
});
function Counter() {
const [state, send] = useMachine(counterMachine);
return (
<div>
<button onClick={() => send('DECREMENT')}>-</button>
{state.context.count}
<button onClick={() => send('INCREMENT')}>+</button>
</div>
);
}
Use XState when…
- Your UI logic can be represented as a state machine. XState shines in applications that benefit from modeling their UI logic as state machines or statecharts. Think apps with complex state logic, multiple states, and transitions, such as forms or games.
- You want to visualize your logic. XState comes with tooling that allows you to visualize your state machines and workflows. This feature is invaluable during both development and debugging phases, especially for large apps.
Valtio
https://github.com/pmndrs/valtio
Github stars: 8k
Last updated: March 2024
Supports TypeScript: yes
Valtio simplifies state management by using a proxy state. Create the state with proxy()
and use it just like a JavaScript object!
It simplifies state mutations and ensures React components automatically update in response. Simply call useSnapshot
with your state object.
import { proxy, useSnapshot } from 'valtio';
const state = proxy({ count: 0 });
function Counter() {
const snapshot = useSnapshot(state);
return (
<div>
<button onClick={() => state.count--}>-</button>
{snapshot.count}
<button onClick={() => state.count++}>+</button>
</div>
);
}
Use Valtio when…
- You need rapid development. Valtio’s lack of boilerplate makes it easy to move fast. It’s ideal for prototypes and small to medium sized projects.
- Your app requires mutable state. Valtio provides an easy and efficient way to manage state mutations directly, without the overhead of immutability patterns.
Conclusion
These examples illustrate the diverse approaches to state management in React, offering solutions ranging from minimalistic and straightforward (like Zustand and Jotai) to more structured and complex (like XState). Valtio provides an easy way to manage state through direct mutations, while MobX focuses on reactive state changes through observables. Each library has its strengths and is suitable for different scenarios depending on the requirements of your React application.
Which of these libraries to you prefer? Do you use one that I left out? Let me know in the comments!
PS. I share content like this post 2x / week. If you’re interested in frontend, backend, and how to become a better full stack developer, subscribe to my newsletter!
Top comments (5)
please correct this example in mobix by replacing state with class
I believe both styles should work, no? According to the docs: mobx.js.org/observable-state.html#...
Yes, both work. But the classy version is much more practical.
Thanks for the suggestion! I updated the example.
Thanks for taking the suggestion :)