Tips & Tricks (Mis à jour: 02/06/2026)

Gestion d'état React avec Claude Code : guide pratique

Organisez l'état React avec Claude Code : Context, Zustand, Jotai, TanStack Query, tests et prompts sûrs.

Gestion d'état React avec Claude Code : guide pratique

La gestion d’état React devient difficile quand toutes les valeurs changeantes sont traitées de la même façon. La valeur d’un champ, l’ouverture d’une modale, un panier, un thème utilisateur et une liste de produits chargée par API sont tous des états, mais ils n’ont ni le même propriétaire ni la même durée de vie.

Claude Code peut aider à remettre de l’ordre, à condition de lui donner une mission précise. “Refactorise le state management” est trop vague. Cela peut créer un store global trop large, dupliquer le cache serveur ou ajouter une librairie sans tests. Le bon flux consiste à demander un inventaire, séparer client state et server state, puis migrer une partie à la fois.

Ce guide explique quand React suffit, quand Zustand, Jotai ou TanStack Query aident vraiment, avec des exemples todo, panier, préférences et produits, des tests, des prompts sûrs, des liens officiels et un CTA de monétisation.

Classer l’état avant de choisir un outil

La première décision est la propriété de la donnée. Est-elle locale au composant, partagée dans le navigateur, persistée comme préférence, ou détenue par le serveur ?

flowchart TD
  A["React state management"] --> B["Local UI state"]
  A --> C["Shared client state"]
  A --> D["Persisted preferences"]
  A --> E["Server state"]
  B --> B1["input, modal, tab"]
  C --> C1["cart, wizard, editor"]
  D --> D1["theme, density, locale"]
  E --> E1["products, orders, profile from API"]

L’état local d’interface reste près du composant. useState suffit pour une valeur simple, useReducer pour plusieurs actions dans le même écran. L’état client partagé concerne des éléments comme un panier ou un éditeur. Les préférences persistées, comme le thème, doivent survivre au rechargement. Le server state vient d’une API et peut changer en dehors de votre application.

Quand React suffit

La documentation officielle Managing State recommande de structurer l’état, de le remonter si besoin, puis de combiner reducer et context quand l’écran grandit. Ce cadre est aussi utile pour guider Claude Code.

BesoinPremier choixEnvisager une librairie quand
Input, tab, modaleuseStatePresque jamais
Écran avec plusieurs actionsuseReducerLe reducer sert à plusieurs routes
Prop drilling profondContextLes mises à jour fréquentes re-render trop
Panier ou brouillonZustandPlusieurs zones éloignées modifient l’état
Thème et densitéJotaiBeaucoup de petites préférences se combinent
Produits, commandes, profil APITanStack QueryCache, retry, refetch et invalidation sont nécessaires

Si une valeur est utilisée dans moins de trois endroits, ne vient pas d’une API et ne doit pas survivre au rechargement, commencez avec React.

Cas 1 : Todo avec useReducer et Context

Une todo list a des actions claires : ajouter, basculer, supprimer. Sans synchronisation serveur, reducer et context sont un bon point de départ.

import {
  createContext,
  useContext,
  useMemo,
  useReducer,
  type Dispatch,
  type ReactNode,
} from "react";

export type Todo = {
  id: string;
  title: string;
  done: boolean;
};

type TodoAction =
  | { type: "added"; title: string }
  | { type: "toggled"; id: string }
  | { type: "deleted"; id: string };

export function todoReducer(todos: Todo[], action: TodoAction): Todo[] {
  switch (action.type) {
    case "added":
      return [
        ...todos,
        { id: crypto.randomUUID(), title: action.title.trim(), done: false },
      ];
    case "toggled":
      return todos.map((todo) =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo,
      );
    case "deleted":
      return todos.filter((todo) => todo.id !== action.id);
    default:
      return todos;
  }
}

const TodoStateContext = createContext<Todo[] | null>(null);
const TodoDispatchContext = createContext<Dispatch<TodoAction> | null>(null);

export function TodoProvider({ children }: { children: ReactNode }) {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const stateValue = useMemo(() => todos, [todos]);

  return (
    <TodoStateContext.Provider value={stateValue}>
      <TodoDispatchContext.Provider value={dispatch}>
        {children}
      </TodoDispatchContext.Provider>
    </TodoStateContext.Provider>
  );
}

export function useTodos() {
  const value = useContext(TodoStateContext);
  if (value === null) throw new Error("useTodos must be used in TodoProvider");
  return value;
}

export function useTodoDispatch() {
  const value = useContext(TodoDispatchContext);
  if (value === null) {
    throw new Error("useTodoDispatch must be used in TodoProvider");
  }
  return value;
}

Cette forme aide Claude Code à travailler proprement. Vous pouvez demander une action edited, une validation contre les titres vides ou des tests du reducer sans toucher toute l’application.

Cas 2 : Panier avec Zustand

Un panier traverse la page produit, le header, le drawer et le checkout. Zustand est pratique pour un store léger sous forme de hook. Le middleware persist permet de garder seulement les champs sûrs après rechargement.

import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";

export type CartItem = {
  productId: string;
  name: string;
  price: number;
  quantity: number;
};

type CartState = {
  items: CartItem[];
  currency: "JPY" | "USD";
  addItem: (item: Omit<CartItem, "quantity">) => void;
  removeItem: (productId: string) => void;
  setQuantity: (productId: string, quantity: number) => void;
  clear: () => void;
  total: () => number;
};

export const useCartStore = create<CartState>()(
  persist(
    (set, get) => ({
      items: [],
      currency: "USD",
      addItem: (item) =>
        set((state) => {
          const current = state.items.find(
            (entry) => entry.productId === item.productId,
          );
          if (current) {
            return {
              items: state.items.map((entry) =>
                entry.productId === item.productId
                  ? { ...entry, quantity: entry.quantity + 1 }
                  : entry,
              ),
            };
          }
          return { items: [...state.items, { ...item, quantity: 1 }] };
        }),
      removeItem: (productId) =>
        set((state) => ({
          items: state.items.filter((item) => item.productId !== productId),
        })),
      setQuantity: (productId, quantity) =>
        set((state) => ({
          items: state.items.map((item) =>
            item.productId === productId
              ? { ...item, quantity: Math.max(1, quantity) }
              : item,
          ),
        })),
      clear: () => set({ items: [] }),
      total: () =>
        get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    }),
    {
      name: "cart-v1",
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({
        items: state.items,
        currency: state.currency,
      }),
    },
  ),
);

Le panier local n’est pas une source de vérité pour le paiement. Le serveur doit confirmer prix, stock, taxe, remise et paiement. Dites à Claude Code quels champs peuvent être persistés et quels champs doivent être validés par l’API.

Cas 3 : Préférences avec Jotai

Thème, densité, sidebar et preview mode sont de petites préférences. Jotai permet de les représenter comme atoms, puis de créer des valeurs dérivées.

import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";

export const themeAtom = atomWithStorage<"light" | "dark">(
  "settings.theme",
  "light",
);

export const densityAtom = atomWithStorage<"comfortable" | "compact">(
  "settings.density",
  "comfortable",
);

export const sidebarOpenAtom = atom(true);

export const settingsLabelAtom = atom((get) => {
  const theme = get(themeAtom) === "dark" ? "dark theme" : "light theme";
  const density =
    get(densityAtom) === "compact" ? "compact layout" : "comfortable layout";
  return `${theme}, ${density}`;
});

export const resetSettingsAtom = atom(null, (_get, set) => {
  set(themeAtom, "light");
  set(densityAtom, "comfortable");
  set(sidebarOpenAtom, true);
});

Le risque est de tout découper en atoms. Jotai est excellent pour des préférences et des valeurs dérivées, pas pour remplacer un cache API ou cacher un workflow métier complexe.

Cas 4 : Produits et commandes avec TanStack Query

Les produits, commandes et profils sont du server state. Ils peuvent devenir obsolètes, échouer, être rechargés ou être modifiés ailleurs. TanStack Query v5 gère fetching, caching, synchronisation et mise à jour de ce type de données.

import {
  QueryClient,
  QueryClientProvider,
  useMutation,
  useQuery,
  useQueryClient,
} from "@tanstack/react-query";
import type { ReactNode } from "react";

type Product = {
  id: string;
  name: string;
  price: number;
  stock: number;
};

const queryClient = new QueryClient();

export function ProductsProvider({ children }: { children: ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
}

async function fetchProducts(category: string): Promise<Product[]> {
  const response = await fetch(`/api/products?category=${category}`);
  if (!response.ok) throw new Error("Failed to fetch products");
  return response.json() as Promise<Product[]>;
}

async function addCartLine(productId: string) {
  const response = await fetch("/api/cart/lines", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ productId }),
  });
  if (!response.ok) throw new Error("Failed to add cart line");
  return response.json();
}

export function useProducts(category: string) {
  return useQuery({
    queryKey: ["products", category],
    queryFn: () => fetchProducts(category),
    staleTime: 60_000,
  });
}

export function useAddCartLine() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addCartLine,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["cart"] });
    },
  });
}

La frontière est simple : Zustand peut garder l’intention temporaire du panier, mais TanStack Query doit lire les produits, commandes, panier confirmé et checkout depuis le serveur.

Prompts sûrs et tests

La page How Claude Code works décrit le cycle contexte, action, vérification. Pour un refactor d’état, demandez d’abord un audit sans modification.

Inspecte la gestion d'état de cette app React. Ne modifie pas encore les fichiers.

Objectifs:
- séparer client state et server state
- identifier ce qui peut rester en useState/useReducer/Context
- justifier Zustand, Jotai ou TanStack Query si nécessaire

Retour attendu:
- inventaire des états
- état dupliqué ou dérivé
- données API stockées comme client state
- ordre de migration sûr avec tests

Testez ensuite la logique pure avant l’interface complète.

import { describe, expect, it, vi } from "vitest";
import { todoReducer, type Todo } from "./TodoProvider";

describe("todoReducer", () => {
  it("adds a todo with a generated id", () => {
    vi.spyOn(crypto, "randomUUID").mockReturnValue("todo-1");

    const result = todoReducer([], { type: "added", title: "Write tests" });

    expect(result).toEqual([
      { id: "todo-1", title: "Write tests", done: false },
    ]);
  });

  it("toggles a todo", () => {
    const initial: Todo[] = [{ id: "todo-1", title: "Ship", done: false }];
    const result = todoReducer(initial, { type: "toggled", id: "todo-1" });
    expect(result[0].done).toBe(true);
  });
});

Pour TanStack Query, suivez le testing guide : créez un QueryClient neuf par test pour éviter les fuites de cache.

Pièges, liens et CTA

Les pièges fréquents sont clairs : tout mettre dans un store global, stocker du server state dans Zustand, dupliquer des valeurs dérivées, persister des informations sensibles, ou accepter le diff de Claude Code sans test, build et vérification manuelle après reload et erreur API.

Références officielles : React Managing State, Zustand persist, Jotai, TanStack Query overview, Claude Code best practices.

Pour améliorer les instructions, lisez le guide de prompts et les conseils de productivité Claude Code. Pour des modèles réutilisables, voyez les produits. Pour appliquer ce workflow à un vrai dépôt d’équipe, utilisez la page training et consultation.

Après essai, le meilleur gain est venu du déplacement du server state vers TanStack Query en premier. Zustand est resté petit, Jotai s’est limité aux préférences, et les changements produits par Claude Code étaient plus faciles à relire grâce à l’inventaire initial.

#Claude Code #React #gestion d'état #Zustand #frontend
Gratuit

PDF gratuit: cheatsheet Claude Code

Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.

Nous protégeons vos données et n'envoyons pas de spam.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.