Aide-mémoire des hooks React : chaque hook avec un exemple interactif

Les hooks React sont des fonctions comme useState, useEffect et useRef qui donnent aux composants fonctions un état, des effets de bord et des refs. Cet aide-mémoire couvre chaque hook intégré de React 19.3 avec son rôle en une ligne, un exemple interactif et son erreur la plus fréquente, plus 12 hooks personnalisés à copier.

Tous les hooks intégrés en un coup d'œil

Au moment où nous écrivons ces lignes (octobre 2026), la dernière version de React est la 19.3. Le tableau liste tous les hooks de la référence officielle des hooks React, plus les trois hooks React 19 documentés sur leurs propres pages (use, useOptimistic et le hook React DOM useFormStatus). Les hooks marqués React 18 fonctionnent dans React 18 et 19. Les hooks marqués React 19 n'existent pas dans React 18. Chaque hook React 18 ci-dessous a un exemple interactif sur lequel vous pouvez cliquer ; ceux de React 19 sont présentés uniquement sous forme de code, car cette page tourne sur React 18.

Les 20 sont affichés
HookGroupeSert àDepuis
useStateÉtatconserver une valeur qui refait le rendu du composant quand elle changeReact 18
useReducerÉtatgérer un état aux nombreuses mises à jour liées via une seule fonction reducerReact 18
useContextContextelire une valeur depuis le provider le plus proche au-dessusReact 18
useRefRefgarder une valeur mutable ou un nœud DOM qui ne déclenche pas de renduReact 18
useImperativeHandleRefchoisir les méthodes qu'un parent peut appeler via une refReact 18
useEffectEffetse synchroniser avec quelque chose hors de React après l'affichageReact 18
useLayoutEffectEffetmesurer la mise en page avant que le navigateur n'afficheReact 18
useInsertionEffectEffetinsérer des règles de style avant les effets de layout (bibliothèques CSS-in-JS)React 18
useEffectEventEffetlire les dernières props dans un effet sans le relancerReact 19.2
useMemoPerformancemettre en cache un calcul coûteux entre les rendusReact 18
useCallbackPerformancemettre en cache une fonction pour que les enfants mémoïsés ne refassent pas leur renduReact 18
useTransitionPerformancemarquer une mise à jour d'état comme non urgente et obtenir un indicateur d'attenteReact 18
useDeferredValuePerformancelaisser une partie lente de l'interface prendre du retard sur une saisie rapideReact 18
useIdAutresgénérer des ID uniques et stables pour les labels et les attributs ARIAReact 18
useSyncExternalStoreAutress'abonner à un store qui vit hors de ReactReact 18
useDebugValueAutresétiqueter un hook personnalisé dans React DevToolsReact 18
useActionStateAutressuivre le résultat et l'état d'attente d'une action de formulaireReact 19
useOptimisticAutresafficher le résultat attendu d'une action avant qu'elle se termineReact 19
useAPIlire une promise ou un contexte, même dans des conditionsReact 19
useFormStatusReact DOMlire l'état d'attente du formulaire parentReact 19

Les hooks suivent deux règles : appelez-les uniquement au niveau supérieur d'un composant ou d'un hook personnalisé (jamais dans des boucles, des conditions ou des fonctions imbriquées), et appelez-les uniquement depuis des composants fonctions React ou d'autres hooks. use est la seule exception à la première règle : il peut être appelé dans des blocs if et des boucles. Le package eslint-plugin-react-hooks vérifie les deux règles pour vous.

Hooks d'état

useState

Ce qu'il fait : Stocke une valeur pour ce composant. Appeler le setter planifie un nouveau rendu avec la nouvelle valeur.

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount((c) => c - 1)}>-1</button>
      <span>{count}</span>
      <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </>
  );
}

Erreur fréquente : Écrire setCount(count + 1) trois fois dans un même clic en espérant +3. Les trois lisent le même count de ce rendu, vous obtenez donc +1. Passez une fonction de mise à jour, setCount(c => c + 1), quand la valeur suivante dépend de la précédente. Le bouton « +3 (updater) » ci-dessus fait exactement cela. Deuxième classique : modifier un objet ou un tableau sur place (user.name = "x"; setUser(user)). React compare par référence, voit le même objet et saute le rendu. Créez plutôt un nouvel objet : setUser({ ...user, name: "x" }).

useReducer

Ce qu'il fait : Déplace la logique de mise à jour dans une seule fonction pure qui reçoit l'état actuel et une action, et renvoie l'état suivant. Utile quand plusieurs valeurs changent ensemble.

type Action = { type: "add"; text: string } | { type: "toggle"; id: number };

function reducer(state: Todo[], action: Action): Todo[] {
  switch (action.type) {
    case "add":
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case "toggle":
      return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
  }
}

const [todos, dispatch] = useReducer(reducer, []);
dispatch({ type: "add", text: "Ship it" });

Erreur fréquente : Faire des effets de bord dans le reducer (requêtes réseau, écriture dans localStorage, génération d'ID aléatoires à partir d'un timer dont vous dépendez ensuite). En développement, le Strict Mode de React appelle les reducers deux fois pour révéler le code impur. Gardez le reducer pur et placez les effets de bord dans des gestionnaires d'événements ou des effets.

Hooks de contexte

useContext

Ce qu'il fait : Lit la valeur du provider correspondant le plus proche au-dessus de ce composant, et refait le rendu quand cette valeur change.

const ThemeContext = createContext<"light" | "dark">("light");

function App() {
  const [theme, setTheme] = useState<"light" | "dark">("light");
  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Badge() {
  const theme = useContext(ThemeContext);
  return <span className={theme}>Current theme: {theme}</span>;
}

Erreur fréquente : Passer un nouvel objet littéral comme valeur du provider à chaque rendu, par exemple value={{ user, setUser }}. Chaque rendu crée un nouvel objet, donc chaque consommateur refait son rendu même si rien n'a changé. Enveloppez la valeur dans useMemo, ou séparez les valeurs qui changent rarement de celles qui changent souvent dans deux contextes. Vérifiez aussi que le provider se trouve au-dessus du composant : appeler useContext dans le composant même qui rend le provider renvoie la valeur par défaut.

Hooks de ref

useRef

Ce qu'il fait : Vous donne une boîte (ref.current) qui survit aux rendus. La modifier ne provoque pas de rendu. Utilisez-la pour les nœuds DOM, les ID de timer et les autres valeurs que l'écran n'affiche pas.

const inputRef = useRef<HTMLInputElement>(null);
const clicks = useRef(0);

<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
<button onClick={() => { clicks.current += 1; }}>Count silently</button>

Erreur fréquente : Afficher ref.current dans le JSX en s'attendant à ce qu'il se mette à jour. Les refs ne déclenchent pas de rendu, donc l'écran ne se met à jour que lorsqu'autre chose refait un rendu (essayez le bouton « Ajouter à la ref », puis « Refaire le rendu » ci-dessus). Si la valeur doit apparaître à l'écran, elle doit être dans l'état. Évitez aussi de lire ou d'écrire des refs pendant le rendu, sauf pour une initialisation paresseuse unique.

useImperativeHandle

Ce qu'il fait : Personnalise ce que reçoit un parent quand il place une ref sur votre composant, pour exposer quelques méthodes nommées au lieu du nœud DOM entier.

type Handle = { shake: () => void; clear: () => void };

// React 18: wrap in forwardRef. In React 19 you can accept ref as a normal prop.
const FancyInput = forwardRef<Handle, Props>(function FancyInput(props, ref) {
  const el = useRef<HTMLInputElement>(null);
  useImperativeHandle(ref, () => ({
    shake() { /* animate */ el.current?.focus(); },
    clear() { if (el.current) el.current.value = ""; },
  }), []);
  return <input ref={el} />;
});

// parent
const ref = useRef<Handle>(null);
<FancyInput ref={ref} />
<button onClick={() => ref.current?.shake()}>Shake</button>

Erreur fréquente : L'utiliser pour piloter ce que des props peuvent exprimer. Si un parent appelle ref.current.open() pour afficher une modale, une prop isOpen est généralement plus simple et plus facile à suivre. Réservez les handles impératifs au focus, au défilement, à la lecture/pause et aux animations. Oublier le tableau de dépendances (le troisième argument) recrée aussi le handle à chaque rendu.

Hooks d'effet

useEffect

Ce qu'il fait : Exécute du code après que React a mis à jour l'écran, pour garder un composant synchronisé avec quelque chose hors de React : un timer, un abonnement, une requête réseau, un widget non React. Renvoyez une fonction de nettoyage pour l'annuler.

useEffect(() => {
  if (!running) return;
  const id = setInterval(() => setSeconds((s) => s + 1), 1000);
  return () => clearInterval(id); // runs before the next effect and on unmount
}, [running]);

Erreur fréquente : Utiliser un effet pour calculer quelque chose que vous pourriez calculer pendant le rendu, par exemple useEffect(() => setFullName(first + " " + last), [first, last]). Cela coûte un rendu supplémentaire et peut afficher des valeurs périmées pendant une frame. Écrivez plutôt const fullName = first + " " + last. Le deuxième bug le plus courant est un nettoyage manquant : en développement, le Strict Mode de React monte, démonte et remonte chaque composant une fois, donc un effet sans nettoyage s'abonnera ou démarrera un timer deux fois. Cette double exécution est un indice, pas un bug de React.

useLayoutEffect

Ce qu'il fait : Même forme que useEffect, mais s'exécute après que React a modifié le DOM et avant que le navigateur n'affiche. Utilisez-le pour mesurer un élément et ajuster la mise en page afin que l'utilisateur ne voie jamais la mauvaise position.

const boxRef = useRef<HTMLSpanElement>(null);
const [width, setWidth] = useState(0);

useLayoutEffect(() => {
  setWidth(boxRef.current!.getBoundingClientRect().width);
}, [text]);

Erreur fréquente : L'utiliser par défaut. Il bloque l'affichage, donc un travail lent à l'intérieur donne l'impression que la page est figée. Utilisez useEffect sauf si vous voyez un scintillement que seule une mesure avant affichage corrige (infobulles, positionnement de popover, dimensionnement automatique). Il ne fait rien non plus pendant le rendu serveur, ce qui produit un avertissement dans certaines configurations.

useInsertionEffect

Ce qu'il fait : S'exécute avant tous les effets de layout pour qu'une bibliothèque puisse insérer des règles <style> avant que quoi que ce soit ne lise la mise en page. Il existe pour les auteurs de bibliothèques CSS-in-JS.

function useScopedRule(selector: string, rule: string) {
  useInsertionEffect(() => {
    const style = document.createElement("style");
    style.textContent = `${selector} { ${rule} }`;
    document.head.appendChild(style);
    return () => style.remove();
  }, [selector, rule]);
}

Erreur fréquente : L'utiliser dans du code applicatif, ou essayer de mettre à jour l'état depuis celui-ci. Vous ne pouvez pas y planifier de mises à jour et les refs ne sont pas encore attachées. Si vous n'écrivez pas une bibliothèque de styles, vous voulez presque certainement useEffect ou useLayoutEffect.

useEffectEvent

Ce qu'il fait : React 19.2 et versions ultérieures. Enveloppe une fonction pour qu'un effet puisse l'appeler et toujours voir les dernières props et le dernier état, sans lister ces valeurs comme dépendances. Utilisez-le pour la partie « événement » d'un effet, par exemple journaliser ou afficher une notification avec le thème actuel.

// React 19.2+ only. Not run on this page.
import { useEffect, useEffectEvent } from "react";

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification("Connected", theme); // always the latest theme
  });

  useEffect(() => {
    const conn = createConnection(roomId);
    conn.on("connected", () => onConnected());
    conn.connect();
    return () => conn.disconnect();
  }, [roomId]); // theme is not a dependency, and onConnected must not be listed
}

Erreur fréquente : Mettre l'effect event dans le tableau de dépendances, l'appeler pendant le rendu ou le passer à d'autres composants. La documentation de React indique de n'appeler les effect events que depuis l'intérieur des effets (ou d'autres effect events), et sa règle de lint avertit quand vous en listez un comme dépendance. Sur React 18, le substitut habituel est le pattern « latest ref » utilisé par useInterval plus bas sur cette page.

Hooks de performance

useMemo

Ce qu'il fait : Met en cache le résultat d'un calcul et ne le recalcule que lorsque ses dépendances changent.

const filtered = useMemo(
  () => items.filter((item) => item.includes(query)),
  [items, query],
);

Erreur fréquente : Tout envelopper dedans. Mémoïser un travail peu coûteux comme a + b ou un petit map ajoute du code et un coût de comparaison sans aucun gain. Mesurez d'abord (Profiler de React DevTools) et mémoïsez les calculs lents, les valeurs passées à des enfants memo et les valeurs utilisées comme dépendances d'effet. Piège voisin : une dépendance qui est un nouvel objet à chaque rendu ({ sort: "asc" }) force le memo à recalculer à chaque fois.

useCallback

Ce qu'il fait : Renvoie la même instance de fonction entre les rendus jusqu'à ce qu'une dépendance change. C'est utile quand la fonction est passée à un enfant enveloppé dans memo, ou utilisée comme dépendance d'effet.

const handleAdd = useCallback(() => setCount((c) => c + 1), []);

const Child = memo(function Child({ onClick }: { onClick: () => void }) {
  return <button onClick={onClick}>Add</button>;
});

<Child onClick={handleAdd} />

Erreur fréquente : Utiliser useCallback quand l'enfant n'est pas mémoïsé. Sans memo sur l'enfant, il refait son rendu avec son parent de toute façon, donc la fonction stable ne change rien. La démo interactive montre les deux enfants : seul celui qui reçoit la fonction mise en cache saute des rendus. Les closures périmées sont l'autre piège : si le callback lit count mais que la liste de dépendances est vide, il continue de voir la première valeur. Utilisez une fonction de mise à jour ou listez la dépendance.

useTransition

Ce qu'il fait : Marque une mise à jour d'état comme non urgente. React garde l'écran actuel réactif pendant qu'il prépare le nouveau, et vous donne un indicateur isPending pour signaler un chargement.

const [isPending, startTransition] = useTransition();

function selectTab(next: string) {
  startTransition(() => {
    setTab(next);
  });
}

Erreur fréquente : Envelopper l'état propre d'un champ texte dans une transition. Les champs contrôlés doivent se mettre à jour en urgence, sinon la saisie semble cassée. Gardez l'état du champ normal et déplacez la mise à jour lente et dérivée dans la transition (ou utilisez useDeferredValue). Dans React 18, la fonction passée à startTransition doit être synchrone ; React 19 accepte aussi les fonctions async.

useDeferredValue

Ce qu'il fait : Vous donne une copie d'une valeur autorisée à prendre du retard. Les parties rapides de l'interface utilisent la vraie valeur ; une partie lente fait son rendu avec la valeur différée et rattrape son retard quand le navigateur est inactif.

const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;

<input value={query} onChange={(e) => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
  <SlowResults query={deferredQuery} />
</div>

Erreur fréquente : Oublier d'envelopper l'enfant lent dans memo. Si l'enfant refait de toute façon son rendu avec le parent à chaque frappe, différer sa prop n'aide pas. Ce n'est pas non plus un debounce : il n'attend pas un temps fixe et ne réduit pas les requêtes réseau. Pour moins de requêtes, utilisez un debounce (voir useDebounce plus bas).

Autres hooks

useId

Ce qu'il fait : Génère un ID unique dans l'application et identique côté serveur et côté client, pour htmlFor, aria-describedby et les attributs similaires.

function Field({ label }: { label: string }) {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

Erreur fréquente : L'utiliser pour créer des clés de liste. Les clés doivent venir de vos données (un ID de la base de données), pas d'un hook. Évitez aussi Math.random() pour les ID d'éléments dans les applications rendues côté serveur : le serveur et le navigateur produisent des nombres différents et l'hydratation échoue, et c'est précisément le problème que useId résout.

useSyncExternalStore

Ce qu'il fait : Abonne un composant à une source de données que React ne possède pas (une API du navigateur, un store global) et lit un snapshot qui reste cohérent pendant le rendu concurrent.

function subscribe(callback: () => void) {
  window.addEventListener("online", callback);
  window.addEventListener("offline", callback);
  return () => {
    window.removeEventListener("online", callback);
    window.removeEventListener("offline", callback);
  };
}

const online = useSyncExternalStore(
  subscribe,
  () => navigator.onLine, // client snapshot
  () => true,             // server snapshot
);

Erreur fréquente : Définir subscribe en ligne dans le composant. Une nouvelle fonction à chaque rendu force React à se désabonner et à se réabonner à chaque fois. Définissez-la hors du composant ou enveloppez-la dans useCallback. La fonction de snapshot doit aussi renvoyer la même valeur quand rien n'a changé : renvoyer un nouvel objet ou tableau à chaque appel provoque une boucle de rendu infinie.

useDebugValue

Ce qu'il fait : Ajoute une étiquette à un hook personnalisé dans React DevTools. Cela n'a aucun effet sur ce que voient les utilisateurs.

function useOnlineStatus() {
  const online = useSyncExternalStore(subscribe, () => navigator.onLine);
  useDebugValue(online ? "Online" : "Offline");
  return online;
}

Erreur fréquente : L'ajouter à chaque hook personnalisé ou y faire un formatage coûteux. Utilisez-le pour les hooks partagés dont l'état est difficile à lire dans DevTools, et passez un formateur en second argument si l'étiquette est coûteuse à construire : useDebugValue(date, d => d.toISOString()).

Hooks React 19 (code uniquement, non exécutables sur React 18)

React 19 uniquement. Les quatre hooks de cette section (use, useActionState, useOptimistic, useFormStatus) ne sont pas exportés par React 18. Cette page tourne elle-même sur React 18, ils sont donc présentés sous forme de code à copier dans un projet React 19, sans démo interactive. useEffectEvent, présenté plus haut, nécessite React 19.2.

useActionState

Ce qu'il fait : Enveloppe une action de formulaire et vous donne son dernier résultat ainsi qu'un indicateur d'attente. L'action reçoit l'état précédent et le FormData soumis.

// React 19+
import { useActionState } from "react";

async function subscribe(prev: { error?: string; ok?: boolean }, formData: FormData) {
  const email = String(formData.get("email") ?? "");
  if (!email.includes("@")) return { error: "Enter a valid email" };
  await saveEmail(email);
  return { ok: true };
}

function Newsletter() {
  const [state, formAction, isPending] = useActionState(subscribe, {});
  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button disabled={isPending}>{isPending ? "Saving..." : "Subscribe"}</button>
      {state.error && <p role="alert">{state.error}</p>}
      {state.ok && <p>Thanks, you are subscribed.</p>}
    </form>
  );
}

Erreur fréquente : Oublier que le premier argument de l'action est l'état précédent. Écrire async function subscribe(formData) signifie que formData est en réalité l'ancien état et que .get lève une erreur. Notez aussi qu'il s'est brièvement appelé useFormState (dans react-dom) pendant la période canary de React 19 ; la documentation actuelle utilise useActionState depuis react.

useOptimistic

Ce qu'il fait : Affiche une version temporaire et optimiste de l'état pendant qu'une action async s'exécute, puis revient à l'état réel quand elle se termine.

// React 19+
import { useOptimistic } from "react";

function Thread({ messages, send }: Props) {
  const [optimistic, addOptimistic] = useOptimistic(
    messages,
    (current, text: string) => [...current, { text, sending: true }],
  );

  async function action(formData: FormData) {
    const text = String(formData.get("text"));
    addOptimistic(text);
    await send(text); // parent updates `messages` when this resolves
  }

  return (
    <form action={action}>
      {optimistic.map((m, i) => <p key={i}>{m.text}{m.sending && " (sending)"}</p>)}
      <input name="text" />
    </form>
  );
}

Erreur fréquente : Appeler le setter optimiste hors d'une action ou d'une transition. React l'attend dans une action de formulaire ou dans startTransition ; ailleurs, la valeur optimiste apparaît et disparaît immédiatement (React émet un avertissement en développement). Et gérez toujours l'échec : si send lève une erreur, affichez-la pour que le message ne disparaisse pas en silence.

use

Ce qu'il fait : Lit la valeur d'une promise (en suspendant jusqu'à sa résolution) ou d'un contexte. Contrairement aux autres hooks, il peut être appelé dans des conditions et des boucles.

// React 19+
import { use, Suspense } from "react";

function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
  const comments = use(commentsPromise); // suspends until resolved
  return comments.map((c) => <p key={c.id}>{c.text}</p>);
}

function Page({ commentsPromise }) {
  return (
    <Suspense fallback={<p>Loading comments...</p>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

// Conditional context read, which useContext cannot do:
function Heading({ show }: { show: boolean }) {
  if (!show) return null;
  const theme = use(ThemeContext);
  return <h1 className={theme}>Hello</h1>;
}

Erreur fréquente : Créer la promise dans le composant qui appelle use, par exemple use(fetch(url)). Chaque rendu crée une nouvelle promise, donc le composant reste suspendu indéfiniment. Créez la promise une seule fois (dans un server component, un loader, un cache ou un parent) et transmettez-la. use ne peut pas non plus se trouver dans un try/catch ; gérez les erreurs avec une error boundary.

useFormStatus

Ce qu'il fait : Importé depuis react-dom. Lit le statut du <form> parent : pending, data, method et action.

// React 19+
import { useFormStatus } from "react-dom";

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>{pending ? "Sending..." : "Send"}</button>;
}

function ContactForm() {
  return (
    <form action={sendMessage}>
      <textarea name="message" />
      <SubmitButton />
    </form>
  );
}

Erreur fréquente : L'appeler dans le composant même qui rend le <form>. Il ne renseigne que sur un formulaire au-dessus du composant appelant, donc dans le composant du formulaire lui-même pending vaut toujours false. Déplacez le bouton dans son propre composant, comme ci-dessus.

Le tableau de dépendances expliqué

useEffect, useLayoutEffect, useMemo, useCallback and useImperativeHandle prennent une liste de valeurs comme dernier argument. React compare chaque élément avec la valeur du rendu précédent à l'aide de Object.is. Si un élément a changé, l'effet s'exécute à nouveau (ou le memo recalcule). Cette seule règle explique tous les cas :

Vous écrivezL'effet s'exécuteUsage typique
useEffect(fn)après chaque rendurare ; signe généralement que quelque chose devrait être calculé pendant le rendu
useEffect(fn, [])une fois après le montage (deux fois en Strict Mode de développement), nettoyage au démontageabonnements et initialisations qui n'utilisent ni props ni état
useEffect(fn, [a, b])après le montage, puis chaque fois que a ou b changechargement par ID, synchronisation avec une prop

Cliquez sur les boutons : changer b relance uniquement l'effet sans tableau, changer a relance celui-ci ainsi que l'effet [a], et l'effet [] reste silencieux après le montage.

Cinq règles qui évitent la plupart des bugs de dépendances

  1. Listez chaque valeur réactive lue par le callback. Les props, l'état et tout ce qui est déclaré dans le corps du composant. En oublier une vous donne une closure périmée : le callback garde la valeur d'un ancien rendu. Installez eslint-plugin-react-hooks et laissez exhaustive-deps vous signaler ce qui manque.
  2. Les objets et fonctions créés pendant le rendu sont nouveaux à chaque fois. { id } ou () => {} n'est jamais égal au précédent selon Object.is, ils relancent donc l'effet à chaque rendu. Déplacez-les dans l'effet, hors du composant, ou mémoïsez-les.
  3. Dépendez de primitives quand c'est possible. [user.id] est plus stable que [user] quand seul l'ID compte.
  4. Utilisez des fonctions de mise à jour pour supprimer des dépendances d'état. setCount(c => c + 1) ne lit pas count, donc count n'a pas besoin d'être listé.
  5. Ne faites pas taire le linter pour « corriger » une boucle. Une boucle infinie signifie généralement que l'effet modifie un état qui est aussi une dépendance, ou dépend d'un objet neuf. Corrigez la cause. Pour les valeurs que vous voulez lire sans y réagir, utilisez useEffectEvent sur React 19.2+, ou une ref que vous maintenez à jour sur React 18.
// Stale closure: logs 0 forever
useEffect(() => {
  const id = setInterval(() => console.log(count), 1000);
  return () => clearInterval(id);
}, []); // count is missing

// Fixed: re-subscribe when count changes
useEffect(() => {
  const id = setInterval(() => console.log(count), 1000);
  return () => clearInterval(id);
}, [count]);

// Fetch with cleanup so a slow old response cannot overwrite a newer one
useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/users/${userId}`, { signal: controller.signal })
    .then((r) => r.json())
    .then(setUser)
    .catch((err) => { if (err.name !== "AbortError") setError(err); });
  return () => controller.abort();
}, [userId]);

12 hooks personnalisés à copier dans votre projet

Un hook personnalisé est une fonction dont le nom commence par use et qui appelle d'autres hooks. Il permet à plusieurs composants de partager une logique avec état ; chaque composant qui l'appelle obtient son propre état indépendant. Les hooks ci-dessous ont été écrits pour cette page en TypeScript, fonctionnent sur React 18 et 19, et n'ont aucune dépendance. Chacun se termine par une note indiquant quand ne pas l'utiliser.

1. useDebounce

Renvoie une valeur qui ne se met à jour qu'une fois que l'entrée a cessé de changer pendant delay millisecondes. Idéal pour les champs de recherche qui appellent une API.

import { useEffect, useState } from "react";

export function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = window.setTimeout(() => setDebounced(value), delay);
    return () => window.clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

// const debouncedQuery = useDebounce(query, 400);
// useEffect(() => { search(debouncedQuery); }, [debouncedQuery]);

Quand ne pas l'utiliser : Quand vous avez seulement besoin que l'interface reste réactive pendant le rendu d'une grande liste. C'est un problème de rendu, et useDeferredValue le résout sans délai artificiel. Évitez-le aussi pour une validation de formulaire qui doit sembler instantanée.

2. useLocalStorage

Fonctionne comme useState, mais enregistre la valeur dans localStorage et la restaure à la visite suivante.

import { useEffect, useState } from "react";

export function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const raw = window.localStorage.getItem(key);
      return raw === null ? initial : (JSON.parse(raw) as T);
    } catch {
      return initial; // private mode, blocked storage or bad JSON
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch {
      /* storage full or blocked: keep the in-memory value */
    }
  }, [key, value]);

  return [value, setValue] as const;
}

Quand ne pas l'utiliser : Pour tout ce qui est sensible (tokens, données personnelles), puisque n'importe quel script de la page peut lire localStorage. Pas non plus sans précaution pour les pages rendues côté serveur : le serveur n'a pas de window, lisez donc le stockage après le montage pour éviter une incohérence d'hydratation. Il ne se synchronise pas entre onglets ; ajoutez un écouteur d'événement storage si vous en avez besoin.

3. useMediaQuery

Renvoie true tant qu'une media query CSS correspond, et se met à jour quand cela change.

import { useCallback, useSyncExternalStore } from "react";

export function useMediaQuery(query: string): boolean {
  const subscribe = useCallback((onChange: () => void) => {
    const mql = window.matchMedia(query);
    mql.addEventListener("change", onChange);
    return () => mql.removeEventListener("change", onChange);
  }, [query]);

  return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(query).matches,
    () => false, // server snapshot
  );
}

// const isDesktop = useMediaQuery("(min-width: 1024px)");
// const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");

Quand ne pas l'utiliser : Pour des changements de mise en page que le CSS peut faire seul. Masquer ou réorganiser des éléments avec les breakpoints Tailwind (md:hidden) fonctionne avant le chargement du JavaScript et ne clignote jamais. Utilisez le hook quand vous devez rendre des composants différents ou éviter complètement de charger quelque chose.

4. useToggle

Un booléen avec une fonction toggle stable, pour les menus, les modales et les sections dépliables.

import { useCallback, useState } from "react";

export function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn((v) => !v), []);
  return [on, toggle, setOn] as const;
}

// const [open, toggleOpen, setOpen] = useToggle();

Quand ne pas l'utiliser : Quand l'état a plus de deux valeurs (fermé, en ouverture, ouvert, erreur). Utilisez une union de chaînes avec useState ou useReducer au lieu d'empiler plusieurs toggles.

5. usePrevious

Renvoie la valeur du rendu précédent, pratique pour afficher une évolution (« en hausse par rapport à 12 ») ou réagir à une transition.

import { useEffect, useRef } from "react";

export function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

Quand ne pas l'utiliser : Pour décider s'il faut mettre à jour l'état. La documentation de React montre un pattern plus propre pour cela : stocker la valeur précédente dans l'état et comparer pendant le rendu. Ce hook lit une ref pendant le rendu, ce qui fonctionne aujourd'hui mais relève du type de code que les recommandations de React demandent de garder rare.

6. useOnClickOutside

Appelle un gestionnaire quand l'utilisateur appuie n'importe où en dehors d'un élément donné. Typique pour fermer un menu ou un popover personnalisé.

import { useEffect, useRef, type RefObject } from "react";

export function useOnClickOutside(
  ref: RefObject<HTMLElement>,
  handler: (event: PointerEvent) => void,
) {
  const saved = useRef(handler);
  useEffect(() => { saved.current = handler; }, [handler]);

  useEffect(() => {
    const listener = (event: PointerEvent) => {
      if (!ref.current || ref.current.contains(event.target as Node)) return;
      saved.current(event);
    };
    document.addEventListener("pointerdown", listener);
    return () => document.removeEventListener("pointerdown", listener);
  }, [ref]);
}

Quand ne pas l'utiliser : Si vous utilisez déjà une bibliothèque headless (Radix, Headless UI) pour le menu ou la boîte de dialogue : elles gèrent pour vous les clics extérieurs, la touche Escape et le retour du focus. Un menu fait main reposant uniquement sur ce hook oublie généralement la prise en charge du clavier.

7. useInterval

Un setInterval qui appelle toujours le dernier callback et peut être mis en pause en passant null comme délai.

import { useEffect, useRef } from "react";

export function useInterval(callback: () => void, delay: number | null) {
  const saved = useRef(callback);
  useEffect(() => { saved.current = callback; }, [callback]);

  useEffect(() => {
    if (delay === null) return;
    const id = window.setInterval(() => saved.current(), delay);
    return () => window.clearInterval(id);
  }, [delay]);
}

// useInterval(() => setTicks((t) => t + 1), running ? 1000 : null);

Quand ne pas l'utiliser : Pour les animations (utilisez CSS ou requestAnimationFrame) ou pour des horloges précises : les intervalles dérivent et les navigateurs les ralentissent dans les onglets en arrière-plan. Pour un compte à rebours, calculez le temps restant à partir d'un timestamp de fin fixe à chaque tick.

8. useWindowSize

Renvoie la largeur et la hauteur actuelles de la fenêtre et se met à jour au redimensionnement.

import { useSyncExternalStore } from "react";

function subscribe(onChange: () => void) {
  window.addEventListener("resize", onChange);
  return () => window.removeEventListener("resize", onChange);
}

export function useWindowSize() {
  const width = useSyncExternalStore(subscribe, () => window.innerWidth, () => 0);
  const height = useSyncExternalStore(subscribe, () => window.innerHeight, () => 0);
  return { width, height };
}

Quand ne pas l'utiliser : Pour les breakpoints : useMediaQuery ne refait le rendu que lorsque la query bascule, alors que celui-ci refait le rendu à chaque pixel de redimensionnement. Pour la taille d'un élément plutôt que de la fenêtre, utilisez un ResizeObserver. (Les deux snapshots séparés sont voulus : renvoyer un nouvel objet depuis une seule fonction de snapshot bouclerait indéfiniment.)

9. useCopyToClipboard

Copie du texte avec la Clipboard API et expose un indicateur copied de courte durée pour le retour visuel du bouton.

import { useCallback, useState } from "react";

export function useCopyToClipboard(resetAfter = 1500) {
  const [copied, setCopied] = useState(false);
  const copy = useCallback(async (text: string) => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      window.setTimeout(() => setCopied(false), resetAfter);
      return true;
    } catch {
      setCopied(false); // insecure context or permission denied
      return false;
    }
  }, [resetAfter]);
  return [copied, copy] as const;
}

Quand ne pas l'utiliser : Sur des pages servies en HTTP simple : navigator.clipboard nécessite un contexte sécurisé (HTTPS ou localhost). Proposez toujours une solution de repli, comme sélectionner le texte, quand il renvoie false.

10. useFetch

Charge le JSON d'une URL, suit l'état de chargement et d'erreur, et annule la requête quand l'URL change ou que le composant est démonté.

import { useEffect, useState } from "react";

type FetchState<T> = { data: T | null; error: Error | null; loading: boolean };

export function useFetch<T>(url: string | null): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({ data: null, error: null, loading: !!url });

  useEffect(() => {
    if (!url) return;
    const controller = new AbortController();
    setState((s) => ({ ...s, loading: true, error: null }));

    fetch(url, { signal: controller.signal })
      .then((res) => {
        if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
        return res.json() as Promise<T>;
      })
      .then((data) => setState({ data, error: null, loading: false }))
      .catch((error: Error) => {
        if (error.name === "AbortError") return; // superseded or unmounted
        setState({ data: null, error, loading: false });
      });

    return () => controller.abort();
  }, [url]);

  return state;
}

Quand ne pas l'utiliser : Dans les applications plus importantes. Il n'a ni cache, ni déduplication, ni nouvelles tentatives, ni revalidation, donc deux composants qui demandent la même URL font deux requêtes. Utilisez le chargement de données de votre framework ou une bibliothèque de données (TanStack Query, SWR) dès que vous avez besoin de l'un de ces éléments. La documentation de React fait la même recommandation pour le chargement de données dans les effets.

11. useEventListener

Attache un écouteur d'événement à la fenêtre (ou à n'importe quel élément) et appelle toujours le dernier gestionnaire, sans se réabonner à chaque rendu.

import { useEffect, useRef } from "react";

export function useEventListener<K extends keyof WindowEventMap>(
  type: K,
  handler: (event: WindowEventMap[K]) => void,
  target: Window | HTMLElement | null = typeof window === "undefined" ? null : window,
) {
  const saved = useRef(handler);
  useEffect(() => { saved.current = handler; }, [handler]);

  useEffect(() => {
    if (!target) return;
    const listener = (event: Event) => saved.current(event as WindowEventMap[K]);
    target.addEventListener(type, listener);
    return () => target.removeEventListener(type, listener);
  }, [type, target]);
}

// useEventListener("keydown", (e) => { if (e.key === "Escape") close(); });

Quand ne pas l'utiliser : Pour les événements que React gère déjà sur les éléments que vous rendez : utilisez onClick, onKeyDown et consorts directement dans le JSX. N'utilisez ce hook que pour window, document ou des éléments tiers.

12. useIsMounted

Renvoie une fonction qui vous indique si le composant est toujours à l'écran, pour du code async ancien qui modifie l'état après un await.

import { useCallback, useEffect, useRef } from "react";

export function useIsMounted() {
  const mounted = useRef(false);
  useEffect(() => {
    mounted.current = true;
    return () => { mounted.current = false; };
  }, []);
  return useCallback(() => mounted.current, []);
}

// const isMounted = useIsMounted();
// const data = await load(); if (isMounted()) setData(data);

Quand ne pas l'utiliser : Par défaut. React 18 a supprimé l'avertissement sur la modification d'état de composants démontés, et la meilleure solution consiste à annuler le travail lui-même (un AbortController, comme dans useFetch) ou à ignorer les résultats périmés avec un indicateur dans le nettoyage de l'effet. Réservez-le à du code ancien que vous ne pouvez pas encore restructurer.

De quel hook ai-je besoin ? Une liste de décision rapide

Vous construisez une bibliothèque de composants plutôt que des hooks isolés ? Notre comparatif des bibliothèques UI React montre les six mêmes composants dans shadcn/ui, Radix, Headless UI, daisyUI, Flowbite et MUI, et l'aide-mémoire Tailwind couvre les classes utilitaires utilisées dans les démos de cette page. Des éléments React et Tailwind prêts à l'emploi se trouvent dans la galerie de composants.

Questions fréquentes

Combien de hooks intégrés React possède-t-il ?

Au moment où nous écrivons ces lignes (octobre 2026, React 19.3), la référence des hooks React liste 17 hooks dans React lui-même : useState, useReducer, useContext, useRef, useImperativeHandle, useEffect, useLayoutEffect, useInsertionEffect, useEffectEvent, useMemo, useCallback, useTransition, useDeferredValue, useDebugValue, useId, useSyncExternalStore et useActionState. React 19 ajoute aussi useOptimistic et l'API use, et React DOM ajoute useFormStatus.

Quelle est la différence entre useMemo et useCallback ?

useMemo met en cache le résultat de l'appel d'une fonction, tandis que useCallback met en cache la fonction elle-même. useCallback(fn, deps) équivaut à useMemo(() => fn, deps). Utilisez useMemo pour les valeurs coûteuses et useCallback pour les fonctions passées à des enfants mémoïsés ou utilisées comme dépendances d'effet.

Pourquoi mon useEffect s'exécute-t-il deux fois ?

En développement avec le Strict Mode activé, React monte chaque composant, le démonte puis le remonte pour vous aider à repérer les effets auxquels il manque un nettoyage. Cela n'arrive pas dans un build de production. Si la double exécution provoque un bug, ajoutez une fonction de nettoyage qui annule ce que l'effet a lancé.

Que signifie un tableau de dépendances vide dans useEffect ?

Un tableau vide indique à React que l'effet ne lit ni props ni état : il s'exécute donc une fois après le montage du composant et son nettoyage s'exécute au démontage. Si l'effet lit des props ou de l'état, listez-les, sinon il continue d'utiliser les valeurs du premier rendu.

Puis-je utiliser des hooks React 19 comme use ou useActionState dans React 18 ?

Non. use, useActionState, useOptimistic et useFormStatus ne sont pas exportés par React 18, et useEffectEvent nécessite React 19.2 ou une version ultérieure. Sur React 18, vous pouvez obtenir un comportement similaire avec useState et des gestionnaires d'événements pour les formulaires, et avec une ref qui contient le dernier callback à la place de useEffectEvent.

Qu'est-ce qui fait d'une fonction un hook personnalisé ?

Son nom commence par use et elle appelle au moins un autre hook. Ce nommage permet aux règles de lint de React de vérifier qu'elle respecte les règles des hooks. Chaque composant qui appelle un hook personnalisé obtient son propre état séparé ; les hooks partagent de la logique, pas des données.

En direct : {title}

+3 (updater)

Réinitialiser

Lire la section useReducer

Ajouter votre propre élément

Nouvel élément

Nouvelle tâche

Effacer les terminées

Je lis « {theme} » depuis le contexte

Basculer la valeur du provider

Cliquez sur le bouton pour me donner le focus

Cible du focus

Focus sur le champ

Ajouter à la ref (sans rendu)

Refaire le rendu

Valeur de la ref affichée au dernier rendu : {n}

Tapez ici

Champ enfant

Le parent appelle shake()

Le parent appelle clear()

effet exécuté : intervalle démarré

nettoyage exécuté : intervalle arrêté

Pause

Reprendre

Redimensionnez-moi en tapant

Texte à mesurer

Mesuré avant affichage : {n}px de large

Couleur de bordure

Cette règle a été injectée dans le head de la page

Filtrer 4,000 éléments

Filtrer les éléments

Rendu sans rapport ({n})

{n} résultats. Le filtre a tourné {runs} fois (les rendus sans rapport ne le relancent pas).

{label} (enfant rendu {n}x)

fonction en ligne

Refaire le rendu du parent

Compteur : {n}

À propos

Liste lente

Contact

Changement en cours (l'ancien onglet reste cliquable)...

Affichage : {tab}

Ligne

Contenu de l'onglet {tab}.

Résultats rendus pour : « {query} »

Tapez vite : le champ reste réactif

Rechercher

Nom

E-mail

Réseau :

Largeur de la fenêtre :

Redimensionnez la fenêtre ou coupez votre réseau pour voir la mise à jour.

Ce composant appelle un hook personnalisé qui s'étiquette « {label} » dans React DevTools. Ouvrez l'onglet Components pour voir l'étiquette à côté du hook.

En ligne

Hors ligne

tableaux de dépendances

[sans tableau] exécuté (rendu {n})

[] exécuté une fois au montage

[a] exécuté, a = {n}

Changer a ({n})

Changer b ({n})

Effacer le journal

Tapez, puis arrêtez une demi-seconde

Champ avec debounce

Instantané : « {text} » · Avec debounce : « {debounced} »

Tapez, puis rechargez la page

Nom enregistré

Enregistré uniquement dans ce navigateur.

Enregistré uniquement dans ce navigateur : « {name} ».

useMediaQuery : le viewport fait {size}, le thème système est {theme}.

768px ou plus

moins de 768px

Activé

Désactivé

Compteur {n}

précédent : {value}

rien pour l'instant

Arrêter

Démarrer

{n} ticks

Copié

Copier une commande

Ouvrir le menu

Cliquez n'importe où en dehors du menu pour le fermer.

Commencer gratuitement · Sans carte bancaire

Voir aussi: Guides

Produit

Pour qui

Fonctionnalités

Ressources

Entreprise

Plan du site