Introduction
State management in JavaScript has evolved beyond Redux. New solutions like Zustand and Jotai offer simpler APIs with better performance.
This guide covers modern state management in 2026.
State Management Options
Comparison
| Library | Type | Boilerplate | Performance |
|---|---|---|---|
| Redux Toolkit | Global | Medium | Good |
| Zustand | Global | Low | Excellent |
| Jotai | Atomic | Low | Excellent |
| Signals | Reactive | Very Low | Excellent |
| useState | Local | None | Good |
Zustand
Installation
npm install zustand
Creating Store
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
user: null,
// Actions
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
setUser: (user) => set({ user }),
// Async actions
fetchUser: async (id) => {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user });
},
}));
Using Store
import { useStore } from './store';
function Counter() {
const { count, increment } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
Jotai
Installation
npm install jotai
Atomic State
import { atom } from 'jotai';
const countAtom = atom(0);
const userAtom = atom(null);
const derivedAtom = atom((get) => get(countAtom) * 2);
Using Atoms
import { useAtom } from 'jotai';
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
Signals
Preact Signals
npm install @preact/signals
Usage
import { signal, computed, effect } from '@preact/signals';
const count = signal(0);
const doubled = computed(() => count.value * 2);
// React component
function Counter() {
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onClick={() => count.value++}>
Increment
</button>
</div>
);
}
Redux Toolkit (Modern Redux)
Setup
npm install @reduxjs/toolkit react-redux
Creating Slice
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
decrement: (state) => { state.value -= 1; },
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
const store = configureStore({
reducer: counterSlice.reducer,
});
Using Redux
import { useDispatch, useSelector } from 'react-redux';
import { increment } from './counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
{count}
</button>
);
}
Best Practices
When to Use What
- Local state: useState, useReducer
- Global simple state: Zustand
- Atomic/derived state: Jotai
- Reactive performance: Signals
- Large complex apps: Redux Toolkit
Performance Tips
// Zustand - selector
const count = useStore((state) => state.count);
// Jotai - derived atom
const doubledAtom = atom((get) => get(countAtom) * 2);
// Redux - memoized selector
const selectDoubled = createSelector(
[(state) => state.counter.value],
(count) => count * 2
);
Conclusion
Modern state management offers many options. Choose based on your needs: Zustand for simplicity, Jotai for atomic state, Signals for performance.
Comments