Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.

LibraryWhat sets it apart
ZustandMinimal code, a store is a single hook
Redux ToolkitStrict structure, reducers and actions, lots of tooling
JotaiState made of small independent pieces — atoms
MobXMutable 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 zustand

A 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.

Thanks for reading!

Donate to the site — we work hard for you.