Leçon 6 / 15

Sécuriser les effets asynchrones

12 min React — données, effets et formulaires Gratuit

Objectif : Empêcher une réponse tardive de modifier l’interface après le démontage du composant.

Une requête peut être encore en cours quand l’utilisateur quitte la page ou que le composant qui l’a déclenchée disparaît. Sans précaution, sa réponse peut alors tenter de modifier un état qui n’est plus utilisé et produire un avertissement ou un traitement inutile. Vous allez associer la requête de dépenses à la durée de vie de l’effet qui la lance.

La leçon précédente a ajouté à `App` des états explicites pour les dépenses, le chargement et l’erreur. Nous conservons ces noms et le service `recupererDepenses`; nous faisons évoluer seulement la façon dont l’effet transmet l’annulation jusqu’à `fetch`.

Relier l’effet à la requête

Un `AbortController` fournit un signal que `fetch` sait écouter. Quand React nettoie l’effet, vous appelez `abort()` sur ce contrôleur : la requête est annulée, et la promesse de `fetch` est rejetée. Le service doit donc accepter le signal, tandis que l’effet reste responsable de décider à quel moment l’annulation a lieu.

src/services/apiDepenses.tsTexte
import type { Depense } from "../types";

export async function recupererDepenses(
  signal?: AbortSignal
): Promise<Depense[]> {
  const response = await fetch("/api/depenses", { signal });

  if (!response.ok) {
    throw new Error(`Erreur HTTP ${response.status}`);
  }

  return (await response.json()) as Depense[];
}

Le paramètre est facultatif : le service peut toujours être appelé sans signal, mais l’effet d’`App` lui en fournit un. La vérification de `response.ok` reste en place ; une réponse HTTP non réussie continue donc de suivre le traitement d’erreur déjà établi.

Nettoyer l’effet et ignorer l’annulation

Le nettoyage d’un effet est la fonction retournée par son callback. React l’exécute lorsque le composant est démonté et avant de relancer l’effet si ses dépendances changent. Ici, l’effet ne dépend d’aucune valeur variable : son nettoyage annule la requête au démontage.

src/App.tsxTexte
import { useEffect, useState } from "react";
import { ListeDepenses } from "./composants/ListeDepenses";
import { recupererDepenses } from "./services/apiDepenses";
import type { Depense } from "./types";

export default function App() {
  const [depenses, setDepenses] = useState<Depense[]>([]);
  const [chargement, setChargement] = useState(true);
  const [erreur, setErreur] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    async function charger() {
      try {
        const donnees = await recupererDepenses(controller.signal);
        setDepenses(donnees);
      } catch (cause: unknown) {
        if (cause instanceof Error && cause.name === "AbortError") {
          return;
        }

        setErreur(
          cause instanceof Error
            ? cause.message
            : "Une erreur inconnue est survenue."
        );
      } finally {
        if (!controller.signal.aborted) {
          setChargement(false);
        }
      }
    }

    void charger();

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

  return (
    <div className="depenses">
      <header className="depenses__entete">
        <h1>Tableau de bord des dépenses</h1>
        <p>Suivez vos dépenses personnelles, vos catégories et votre budget mensuel.</p>
      </header>
      <main className="depenses__contenu">
        <section className="depenses__panneau" aria-labelledby="titre-liste">
          <h2 className="depenses__titre" id="titre-liste">Dépenses</h2>
          <div className="depenses__etat" role="status" aria-live="polite">
            {chargement ? "Chargement des dépenses…" : erreur ?? ""}
          </div>
          {!chargement && !erreur && <ListeDepenses depenses={depenses} />}
        </section>
      </main>
    </div>
  );
}

Le `catch` distingue l’annulation d’une erreur à signaler. Le `finally` ne remet `chargement` à faux que si la requête n’a pas été annulée : après démontage, aucune mise à jour d’état n’est effectuée. Les erreurs réseau et HTTP, elles, continuent d’alimenter `erreur`.

Vérifier le comportement

Quand la réponse arrive à temps, les dépenses sont enregistrées et la liste s’affiche comme auparavant. Si le composant disparaît avant la réponse, le nettoyage déclenche l’annulation ; le rejet correspondant est ignoré et aucune dépense, erreur ou fin de chargement n’est écrite dans l’état du composant démonté.

À retenir

  • Créez un AbortController dans l’effet et transmettez son signal à fetch.
  • Retournez un nettoyage qui appelle controller.abort().
  • Ignorez AbortError : une annulation n’est pas une erreur à afficher.
  • Évitez les mises à jour d’état dans le traitement d’annulation, y compris dans finally.

La suite du parcours portera sur les champs contrôlés du formulaire de saisie des dépenses.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.