Lightweight State

Zustand

Zustand est un store d'état minuscule, rapide et sans opinion pour React. Pas de providers, pas de boilerplate, et des abonnements basés sur des sélecteurs qui minimisent les re-renders.

intermediate13 min readUpdated 15 sept. 2026
store.js
js
// store.js
import { create } from "zustand";

export const useStore = create((set) => ({
  count: 0,
  increment: () =>
    set((state) => ({ count: state.count + 1 })),
  reset: () => set({ count: 0 }),
}));
Taille
Environ 1kB
Provider
Non requis
Création avec
create
Lecture avec
Sélecteurs
Async
Fonctions async classiques
Middleware
persist, devtools, immer

Pourquoi c'est important

Pourquoi Zustand est rafraîchissant

Minuscule et rapide

Environ un kilo-octet avec des abonnements basés sur des sélecteurs, pour que les composants ne se re-rendent que lorsque la valeur exacte qu'ils utilisent change.

Presque aucun boilerplate

Un store se crée via un simple appel de fonction. Il n'y a pas de providers, de reducers, de types d'actions ou de wrappers de contexte à configurer.

Sans opinion

Gardez l'état et les actions ensemble, divisez-les en slices ou utilisez des middleware. Zustand ne s'impose pas à vous.

Le tableau complet

Les trois idées derrière Zustand

Un hook, un objet simple contenant l'état et les actions, et des abonnements basés sur des sélecteurs. Voilà tout le modèle.

Le store

État

Un hook créé avec create qui détient l'état et les actions qui le mettent à jour.

Sélecteurs

Abonnements

Les composants lisent exactement la slice dont ils ont besoin et ne se re-rendent que lorsqu'elle change.

Actions

Mises à jour

Des fonctions simples qui appellent set pour fusionner le nouvel état, sans besoin de reducers ou de dispatch.

Zustand en un coup d'œil

Le cœur de Zustand

create

Construisez un hook de store avec l'état et les actions au même endroit.

Sélecteurs

Abonnez-vous à une slice de l'état pour que les changements non liés ne provoquent pas de re-render.

set et get

Mettez à jour l'état avec set et lisez-le de manière impérative avec get.

Actions async

Écrivez des fonctions async directement dans le store, aucun middleware n'est requis.

persist middleware

Sauvegardez et réhydratez l'état dans le localStorage ou un autre stockage.

Middleware

Ajoutez devtools, des aides à l'immuabilité et la persistance en composant des fonctions.

Un bref aperçu

Petite bibliothèque, grande adoption

  1. 2019

    Sortie de Zustand

    Un petit store créé par les développeurs de Jotai et de l'écosystème React Spring.

    19
  2. 2020

    Adoption croissante

    Les développeurs lassés du boilerplate l'adoptent pour sa simplicité et son modèle de sélecteurs.

    20
  3. 2022

    Zustand 4

    Meilleur support TypeScript et un cœur plus petit et flexible.

    22
  4. 2024

    Zustand 5

    Une API plus propre et une compatibilité améliorée avec React 18 et 19.

    24
  5. Aujourd'hui

    Un standard moderne

    Un choix courant pour l'état global client dans les projets React de toutes tailles.

    Aujourd'hui

Le guide complet

Zustand: Tout ce que vous devez savoir

Qu’est-ce que Zustand ?

Zustand est un gestionnaire d’état léger et rapide pour React. L’intégralité de son API est facile à mémoriser : vous appelez create avec une fonction qui retourne l’état et les actions, et vous obtenez en retour un hook. Il n’y a ni provider, ni reducer, ni types d’actions, ni dispatch. Vous importez le hook et vous l’utilisez.

Son nom signifie « état » en allemand, et la bibliothèque est fidèle à cette simplicité. Elle pèse environ un kilo-octet, utilise des abonnements basés sur des sélecteurs pour minimiser les re-renders, et fonctionne avec JavaScript et TypeScript. Pour de nombreuses applications React, c’est la solution qui demande le moins de formalités pour partager un état globalement.

Créer un store

Un store est un hook. L’état et les fonctions qui le mettent à jour cohabitent au même endroit.

// store.js
import { create } from "zustand";

export const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),

  double: () => get().count * 2,
}));

set fusionne l’objet retourné dans l’état, et get lit l’état actuel de manière impérative. Il n’y a aucun reducer à écrire ni d’action à dispatcher — seulement des fonctions qui mettent à jour l’état.

Lire l’état avec des sélecteurs

Les composants s’abonnent précisément à la tranche d’état dont ils ont besoin.

// Counter.jsx
import { useStore } from "./store";

export function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);

  return <button onClick={increment}>{count}</button>;
}

Comme le composant s’abonne uniquement à state.count, les mises à jour de parties non liées du store ne provoquent pas son re-rendu. C’est là la différence majeure de performance avec l’ Context API de React, qui re-rend chaque consommateur dès que la valeur change.

Pour sélectionner plusieurs valeurs, utilisez une comparaison superficielle (shallow comparison) afin que le composant ne se re-rende que lorsqu’une de ces valeurs change réellement :

// shallow.js
import { useShallow } from "zustand/react/shallow";

const { count, increment } = useStore(
  useShallow((state) => ({ count: state.count, increment: state.increment })),
);

Mise à jour de l’état

set effectue une fusion par défaut, vous ne spécifiez donc que ce qui a changé. Passez une fonction lorsque la valeur suivante dépend de la précédente.

// updates.js
set({ count: 0 }); // merge
set((state) => ({ count: state.count + 1 })); // derive from previous
set((state) => ({ items: [...state.items, item] })); // arrays

Pour les objets imbriqués, set remplace la clé de premier niveau ; utilisez donc l’opérateur spread sur l’objet existant lorsque vous devez conserver les autres propriétés. Le middleware Immer est disponible si vous préférez une syntaxe de mutation pour les mises à jour profondes.

Actions asynchrones

La logique asynchrone se résume simplement à une fonction async dans le store. Il n’y a aucun middleware à configurer.

// usersStore.js
import { create } from "zustand";

export const useUsers = create((set) => ({
  users: [],
  loading: false,
  error: null,

  fetchUsers: async () => {
    set({ loading: true, error: null });
    try {
      const res = await fetch("/api/users");
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      set({ users: await res.json(), loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

Les indicateurs de chargement (loading) et d’erreur (error) sont stockés juste à côté des données, et les composants ne sélectionnent que les éléments qu’ils doivent afficher.

Slices et middleware

Pour un store volumineux, divisez-le en slices qui seront ensuite combinées en un seul store. Chaque slice est une fonction qui reçoit set et get.

// slices/cart.js
export const createCartSlice = (set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  clear: () => set({ items: [] }),
});
// store.js
import { create } from "zustand";
import { createCartSlice } from "./slices/cart";
import { createUserSlice } from "./slices/user";

export const useStore = create((...args) => ({
  ...createCartSlice(...args),
  ...createUserSlice(...args),
}));

Le middleware se compose comme des fonctions autour du créateur du store. Les plus courants sont :

  • persist sauvegarde et réhydrate l’état dans localStorage ou un autre stockage.
  • devtools connecte le store à l’extension Redux DevTools.
  • immer vous permet d’écrire des mises à jour mutables tout en restant immuables.
// persisted.js
import { persist } from "zustand/middleware";

export const useSettings = create(
  persist(
    (set) => ({
      theme: "dark",
      setTheme: (theme) => set({ theme }),
    }),
    { name: "settings" },
  ),
);

Quand utiliser Zustand

Zustand est idéal pour l’état global client : thèmes et préférences, paniers d’achat, état de l’UI partagé entre des composants éloignés, brouillons, filtres et état des modales. C’est particulièrement utile lorsque l’utilisation de Context provoquerait trop de re-renders ou quand Redux semble apporter une structure plus complexe que nécessaire.

Ne l’utilisez pas pour l’état serveur. Les données provenant d’une API nécessitent du caching, de la déduplication, du rafraîchissement en arrière-plan et de l’invalidation, et TanStack Query gère bien mieux ces problématiques. La configuration courante consiste à utiliser Zustand pour l’état client et TanStack Query pour l’état serveur.

Bonnes pratiques

  • Lisez toujours l’état via un selector.
  • Utilisez useShallow lors de la sélection de plusieurs valeurs.
  • Gardez les actions dans le store pour que les composants ne connaissent pas la structure de l’état.
  • Divisez les stores volumineux en slices.
  • Utilisez le persist middleware pour les paramètres qui doivent être conservés après le rechargement de la page.
  • Ne stockez pas les données serveur dans le store.
  • Stockez, dans la mesure du possible, des données simples et sérialisables.

Erreurs courantes

  • Appeler useStore() sans sélecteur, ce qui provoque un re-rendu à chaque modification.
  • Retourner un nouvel objet depuis un sélecteur sans effectuer de comparaison superficielle (shallow comparison).
  • Utiliser Zustand comme un cache d’état serveur.
  • Muter un état imbriqué sans Immer, entraînant la perte de mises à jour.
  • Créer un store à l’intérieur d’un composant, ce qui le réinitialise à chaque rendu.
  • Ajouter une complexité inutile (ceremony) typique de Redux, alors que Zustand a été conçu pour s’en affranchir.

Et après ?

Zustand représente souvent le juste milieu entre Context et Redux. Comparez-le avec Context pour un partage simple et avec Redux Toolkit pour un état strictement typé et outillé, et déportez vos données serveur vers TanStack Query. Pour Vue, c’est Pinia qui remplit ce même rôle.

S'abonner à l'état

Sélectionnez uniquement ce dont le composant a besoin. Sélectionner tout le store provoque un re-render à chaque changement.

Préférer
const count = useStore((s) => s.count);
const increment = useStore((s) => s.increment);
Éviter
const { count, other, more } = useStore();
// re-renders on any change

Définir les mises à jour

Gardez les actions dans le store pour que les composants n'aient pas besoin de connaître la structure de l'état.

Préférer
const useStore = create((set) => ({
  count: 0,
  increment: () =>
    set((s) => ({ count: s.count + 1 })),
}));
Éviter
const useStore = create(() => ({
  count: 0,
}));

// component does
// useStore.setState({ count: 1 })

FAQ

Foire aux questions

Keep learning

Related topics from the roadmap.

$ commencer à apprendre

Prêt à apprendre Zustand ?

Notre tutoriel interactif vous guide à travers Zustand pas à pas — avec des quiz et du vrai code que vous pouvez exécuter dans le navigateur.