// Web

Les Hooks React : useState, useEffect et les autres

Les Hooks ont transformé la façon d’écrire React. Introduits pour permettre aux composants fonctions de gérer un état et des effets, ils ont rendu les classes largement obsolètes. Un hook est simplement une fonction spéciale, préfixée par use, qui « accroche » votre composant à des fonctionnalités de React. Ce guide passe en revue les hooks essentiels, leurs pièges, et la création de vos propres hooks.

useState : gérer l’état local

useState est le hook fondateur. Il ajoute une variable d’état à un composant fonction et déclenche un nouveau rendu à chaque modification.

import { useState } from "react";

function Compteur() {
  const [compte, setCompte] = useState(0);

  return (
    <button onClick={() => setCompte(compte + 1)}>
      Cliqué {compte} fois
    </button>
  );
}

useState renvoie un tableau de deux éléments : la valeur actuelle et une fonction pour la modifier. On les récupère par déstructuration. Point crucial : ne modifiez jamais l’état directement (compte++), utilisez toujours la fonction de mise à jour, sinon React ne détecte pas le changement.

Quand la nouvelle valeur dépend de l’ancienne, préférez la forme fonctionnelle pour éviter les bugs de valeur périmée :

// ✅ Fiable : React fournit la valeur la plus à jour
setCompte((prev) => prev + 1);

// ⚠️ Risqué si plusieurs mises à jour s'enchaînent
setCompte(compte + 1);

Si les bases de React ne sont pas encore acquises, notre guide React pour débuter présente les composants et le JSX avant d’attaquer les hooks.

useEffect : gérer les effets de bord

useEffect sert à synchroniser votre composant avec l’extérieur : appels réseau, abonnements, minuteurs, manipulation directe du DOM. Il s’exécute après le rendu.

import { useState, useEffect } from "react";

function Profil({ userId }: { userId: number }) {
  const [nom, setNom] = useState<string | null>(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((r) => r.json())
      .then((data) => setNom(data.nom));
  }, [userId]); // relancé uniquement quand userId change

  return <p>{nom ?? "Chargement..."}</p>;
}

Le tableau de dépendances

Le second argument, le tableau de dépendances, contrôle quand l’effet se relance. Trois cas de figure :

useEffect(() => { /* ... */ });        // à CHAQUE rendu (rarement voulu)
useEffect(() => { /* ... */ }, []);    // une SEULE fois, au montage
useEffect(() => { /* ... */ }, [a, b]); // quand a OU b change

La règle d’or : toute valeur utilisée dans l’effet et issue du composant doit figurer dans les dépendances. Oublier une dépendance est la première source de bugs avec useEffect (valeurs figées, données non rafraîchies).

Le nettoyage (cleanup)

Si votre effet crée quelque chose qui doit être défait (un abonnement, un minuteur, un écouteur d’événement), retournez une fonction de nettoyage. React l’appelle avant de relancer l’effet et au démontage du composant.

useEffect(() => {
  const id = setInterval(() => console.log("tic"), 1000);

  // Nettoyage : indispensable, sinon le minuteur fuit
  return () => clearInterval(id);
}, []);

Sans ce nettoyage, vous accumulez des minuteurs ou des abonnements fantômes qui provoquent fuites mémoire et comportements imprévisibles.

useRef : une valeur persistante sans re-rendu

useRef conserve une valeur mutable qui survit aux rendus sans en déclencher. Ses deux usages principaux : accéder à un élément du DOM, et stocker une valeur « hors état ».

import { useRef, useEffect } from "react";

function ChampAutoFocus() {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus(); // accès direct au DOM
  }, []);

  return <input ref={inputRef} placeholder="Focus automatique" />;
}

Contrairement à useState, modifier ref.current ne relance pas le rendu. C’est exactement ce qu’on veut pour garder l’identifiant d’un minuteur ou la valeur précédente d’une prop, sans provoquer d’affichage inutile.

useMemo et useCallback : optimiser les recalculs

Ces deux hooks servent à la mémoïsation : éviter de recalculer ou recréer quelque chose à chaque rendu. useMemo mémorise une valeur ; useCallback mémorise une fonction.

import { useMemo, useCallback } from "react";

function Liste({ items, filtre }: { items: string[]; filtre: string }) {
  // useMemo : ne refiltre que si items ou filtre changent
  const visibles = useMemo(
    () => items.filter((i) => i.includes(filtre)),
    [items, filtre]
  );

  // useCallback : garde la même référence de fonction entre rendus
  const onClick = useCallback((item: string) => {
    console.log("clic sur", item);
  }, []);

  return (
    <ul>
      {visibles.map((i) => (
        <li key={i} onClick={() => onClick(i)}>{i}</li>
      ))}
    </ul>
  );
}

useCallback(fn, deps) est en réalité un raccourci pour useMemo(() => fn, deps). On l’utilise surtout pour passer une fonction stable à un composant enfant optimisé, afin d’éviter des re-rendus inutiles. Attention cependant : n’optimisez pas prématurément. Ces hooks ont un coût, et sur des calculs légers ils n’apportent rien, voire dégradent la lisibilité.

useContext : partager des données sans props

useContext lit une valeur fournie par un Context, ce qui évite de faire descendre une prop à travers de nombreux niveaux (le fameux prop drilling). Idéal pour un thème, une langue ou l’utilisateur connecté.

import { createContext, useContext } from "react";

const ThemeContext = createContext<"clair" | "sombre">("clair");

function Bouton() {
  const theme = useContext(ThemeContext); // lit la valeur la plus proche
  return <button className={theme}>Valider</button>;
}

function App() {
  return (
    <ThemeContext.Provider value="sombre">
      <Bouton />
    </ThemeContext.Provider>
  );
}

Les règles des hooks

Deux règles impératives, vérifiées par l’outillage React, garantissent le bon fonctionnement des hooks :

  1. Appelez les hooks uniquement au niveau supérieur. Jamais dans une condition, une boucle ou une fonction imbriquée. React s’appuie sur l’ordre d’appel pour associer chaque hook à son état ; un appel conditionnel casse cet ordre.
  2. Appelez les hooks uniquement depuis un composant React ou un autre hook. Pas depuis une fonction JavaScript ordinaire.
function Mauvais({ actif }: { actif: boolean }) {
  if (actif) {
    const [x, setX] = useState(0); // ❌ hook dans une condition
  }
}

function Bon({ actif }: { actif: boolean }) {
  const [x, setX] = useState(0);   // ✅ toujours appelé, au sommet
  if (actif) { /* utilise x ici */ }
}

Créer un hook personnalisé

Dès qu’une logique à base de hooks se répète, extrayez-la dans un hook personnalisé : une fonction dont le nom commence par use et qui appelle d’autres hooks. C’est le mécanisme de réutilisation le plus élégant de React.

import { useState, useEffect } from "react";

// Hook réutilisable : lire/écrire dans le localStorage
function useLocalStorage<T>(cle: string, initial: T) {
  const [valeur, setValeur] = useState<T>(() => {
    const brut = localStorage.getItem(cle);
    return brut ? (JSON.parse(brut) as T) : initial;
  });

  useEffect(() => {
    localStorage.setItem(cle, JSON.stringify(valeur));
  }, [cle, valeur]);

  return [valeur, setValeur] as const;
}

// Utilisation, aussi simple que useState
function Preferences() {
  const [langue, setLangue] = useLocalStorage("langue", "fr");
  return (
    <select value={langue} onChange={(e) => setLangue(e.target.value)}>
      <option value="fr">Français</option>
      <option value="en">English</option>
    </select>
  );
}

Le hook encapsule toute la logique de synchronisation ; le composant, lui, reste limpide. C’est ainsi qu’on partage du comportement stateful entre composants sans dupliquer de code.

Les pièges les plus fréquents

  • Dépendances manquantes dans useEffect → valeurs figées et données non rafraîchies. Complétez le tableau, ou repensez l’effet.
  • Oublier le nettoyage → fuites mémoire (minuteurs, abonnements accumulés).
  • Muter l’état directement (items.push(...)) → React ne détecte pas le changement. Créez une nouvelle référence (setItems([...items, x])).
  • Sur-optimiser avec useMemo/useCallback → complexité inutile pour un gain nul sur des calculs légers.
  • Appeler un hook conditionnellement → viole les règles des hooks et casse l’état.

En résumé

Les hooks donnent aux composants fonctions toute la puissance de React : état avec useState, effets de bord avec useEffect, références avec useRef, optimisation avec useMemo/useCallback, et partage de données avec useContext. Respectez les deux règles fondamentales, soignez vos tableaux de dépendances, et extrayez vos logiques récurrentes dans des hooks personnalisés. Vous tenez là l’essentiel du React moderne.

À lire ensuite