State management libraries
For most tasks useState, useReducer and context are enough. But when shared data is needed by dozens of components in different parts of the app and changes often, context becomes awkward: any change re-renders everyone who reads it. Then you use a state library — an external store from which a component takes only the part it needs.
Popular libraries
| Library | What sets it apart |
|---|---|
| Zustand | Minimal code, a store is a single hook |
| Redux Toolkit | Strict structure, reducers and actions, lots of tooling |
| Jotai | State made of small independent pieces — atoms |
| MobX | Mutable objects that report their own changes |
Zustand is the easiest place to start: it is small and resembles hooks you already know.
Installing
npm install zustandA store
Let us add favourite posts to the blog, visible in the menu and in the list. Create src/store/useFavorites.js:
// src/store/useFavorites.js
import { create } from 'zustand';
export const useFavorites = create((set) => ({
ids: [],
toggle: (id) =>
set((state) => ({
ids: state.ids.includes(id)
? state.ids.filter((item) => item !== id)
: [...state.ids, id],
})),
}));create takes a function that returns the initial data and the functions that change it. set replaces the given fields and leaves the rest as they are. The rule is the same as in React: do not change the array in place, create a new one.
Using it in components
The store is a hook. A component passes it a selector function that picks the part it needs:
// src/components/FavoriteButton.jsx
import { useFavorites } from '../store/useFavorites.js';
export default function FavoriteButton({ postId }) {
const isFavorite = useFavorites((state) => state.ids.includes(postId));
const toggle = useFavorites((state) => state.toggle);
return <button onClick={() => toggle(postId)}>{isFavorite ? '★' : '☆'}</button>;
}// src/components/FavoritesCount.jsx
import { useFavorites } from '../store/useFavorites.js';
export default function FavoritesCount() {
const count = useFavorites((state) => state.ids.length);
return <span>Favourites: {count}</span>;
}Render <FavoritesCount /> in the Nav menu and <FavoriteButton postId={post.id} /> next to each title in the post list. Clicking a star fills it in, and the counter in the menu grows. No provider wrapper is needed as with context: the store is available from any component.
Re-rendering only what is needed
A component re-renders only when the value returned by its selector changes. The FavoritesCount counter updates when any post is added, while a star updates only for the post that was clicked.
When you need it
Do not add a library up front. Keep state in components first and lift it as needed. If props have to be passed through many levels and context re-renders too much, it is time to bring in a store.
Common mistake
Calling the hook without a selector subscribes the component to the whole store:
// Wrong: the component re-renders on any change in the store.
const { ids } = useFavorites();
// Correct: only when ids changes.
const ids = useFavorites((state) => state.ids);Select exactly the data the component needs.