Aller au contenu principal

useEffect et nettoyage

Lancer une minuterie quand le composant apparaît, et surtout l'arrêter quand il disparaît.

Objectifs de la séance​

  • comprendre ce qu'est un effet et quand useEffect s'exécute ;
  • maîtriser le tableau de dépendances ([], [valeur], absent) ;
  • écrire la fonction de nettoyage (return () => ...) ;
  • observer ce que fait StrictMode et ce qui se passe sans nettoyage ;
  • mettre à jour le titre de l'onglet du navigateur.

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. jouer une partie et vérifier que le double clic sur une réponse n'ajoute qu'un point
  2. ouvrir src/logic/partieReducer.ts et citer les deux types d'action possibles
  3. dire ce que renvoie le reducer lorsqu'on envoie SUIVANTE alors que le statut est "question"
📌 Réponse attendue

Notions théoriques​

Qu'est-ce qu'un effet ?​

Le corps d'un composant doit rester pur : il calcule le JSX à afficher à partir de l'état et des props, sans rien faire d'autre. Tout ce qui touche à l'extérieur (minuterie, réseau, titre de la page, abonnement) est un effet, et se place dans useEffect. Il s'exécute après l'affichage.

import { useEffect } from "react";

useEffect(() => {
console.log("Le composant vient d'être affiché");
}, []);

Le tableau de dépendances​

Le deuxième argument indique quand l'effet s'exécute :

ÉcritureL'effet s'exécute...
useEffect(effet, [])une seule fois, après le premier affichage
useEffect(effet, [secondes])après le premier affichage, puis chaque fois que secondes change
useEffect(effet) (sans tableau)après chaque affichage (presque toujours une erreur)

Règle : le tableau liste toutes les variables (états, props) utilisées par l'effet. Sinon, l'effet travaille avec une valeur périmée (c'est le piège des fermetures vu dans le cours JavaScript). L'outil npm run lint signale les dépendances oubliées.

La fonction de nettoyage​

Un effet peut renvoyer une fonction : c'est son nettoyage. React l'exécute :

  • avant de relancer l'effet (quand une dépendance change) ;
  • quand le composant disparaît de l'écran.
src/components/Chronometre.tsx
import { useEffect, useState } from "react";
import { formaterDuree } from "../logic/duree";

export function Chronometre() {
const [secondes, setSecondes] = useState(0);

useEffect(() => {
const minuterie = setInterval(() => {
setSecondes((ancien) => ancien + 1);
}, 1000);

return () => {
clearInterval(minuterie);
};
}, []);

return <p>Temps : {formaterDuree(secondes)}</p>;
}

Remarquez la mise à jour fonctionnelle setSecondes((ancien) => ancien + 1) : la minuterie est créée une seule fois, avec la première valeur de secondes. Écrire setSecondes(secondes + 1) ajouterait toujours 1 à 0 : la valeur périmée !

Bonne pratique - Tout ce qui démarre doit s'arrêter

Chaque fois qu'un effet démarre quelque chose (minuterie, requête, abonnement), demandez-vous : « qui l'arrête ? ». La réponse est la fonction de nettoyage.

Pourquoi le nettoyage est indispensable​

Quand la partie se termine, Chronometre disparaît de l'écran. Sans nettoyage, la minuterie continuerait à tourner, à mettre à jour un composant qui n'existe plus, et à consommer de la mémoire. À chaque nouvelle partie, une minuterie supplémentaire s'ajouterait.

StrictMode : le détecteur de nettoyages oubliés​

En développement, StrictMode (dans main.tsx) exécute chaque effet deux fois au démarrage : lancement, nettoyage, relancement. Si votre nettoyage est correct, vous ne voyez aucune différence. S'il manque :

  • la minuterie est lancée deux fois, la première n'est jamais arrêtée ;
  • le chronomètre avance deux fois trop vite ! (essayez en supprimant le return () => ... : le compteur passe de 2 en 2.)

C'est un excellent détecteur : un bug caché en production devient visible pendant le développement.

Un deuxième effet : le titre de l'onglet​

Plusieurs effets peuvent coexister dans un même composant, chacun avec son rôle et ses dépendances.

useEffect(() => {
document.title = `Quiz Cyber (${formaterDuree(secondes)})`;

return () => {
document.title = "Quiz Cyber"; // on restaure le titre d'origine
};
}, [secondes]);

Cet effet s'exécute à chaque changement de secondes (donc chaque seconde), et son nettoyage restaure le titre quand le composant disparaît.

Quand ne PAS utiliser un effet​

  • réagir à un clic : le code va dans la fonction appelée par onClick, pas dans un effet ;
  • calculer une valeur à partir de l'état (un pourcentage, une liste filtrée) : calculez-la directement pendant le rendu, sans effet ni état supplémentaire.

Un effet sert uniquement à synchroniser le composant avec le monde extérieur.


Test de mémorisation/compréhension​


Quand s'exécute useEffect(() => { ... }, []) ?


Quand s'exécute useEffect(effet, [secondes]) ?


À quoi sert la fonction renvoyée par un effet (return () => ...) ?


Que se passe-t-il si on oublie clearInterval dans le nettoyage ?


Pourquoi écrire setSecondes((ancien) => ancien + 1) dans la minuterie ?


Quel effet de StrictMode révèle un nettoyage oublié ?



TP pour réfléchir et résoudre des problèmes​

Votre mission : ajouter un chronomètre à la partie, avec le titre de l'onglet synchronisé.

Étape 1 — Mettre le temps en forme​

Créez src/logic/duree.ts : formaterDuree transforme un nombre de secondes en « mm:ss » (75 secondes donnent 01:15), sans jamais afficher NaN.


Bonne pratique - Protéger les fonctions d'affichage

Une valeur négative ou NaN est ramenée à 0 : l'écran n'affichera jamais « NaN:NaN ». C'est une ligne de protection pour un bug évité.

Étape 2 — La minuterie​

Créez src/components/Chronometre.tsx. Complétez la mise à jour du compteur dans la minuterie.


Bonne pratique - Mise à jour fonctionnelle dans les minuteries

Une minuterie garde la valeur de secondes du rendu où elle a été créée. La forme fonctionnelle lit toujours la dernière valeur.

Étape 3 — Le nettoyage​

Écrivez la fonction de nettoyage qui arrête la minuterie.


Bonne pratique - Vérifier le nettoyage

Mettez en commentaire clearInterval(minuterie);, relancez la page et regardez le chrono avancer de 2 en 2 : c'est StrictMode qui révèle l'oubli. Remettez ensuite la ligne.

Étape 4 — Le titre de l'onglet​

Ajoutez un second effet qui met à jour document.title à chaque seconde.


Bonne pratique - Un effet, un rôle

Deux effets distincts (minuterie, titre) sont plus lisibles qu'un seul effet qui ferait tout, et chacun a ses propres dépendances.

Étape 5 — Afficher le chronomètre dans la partie​

Dans Partie.tsx, importez Chronometre et placez-le au début de la carte de la question.


Bonne pratique - Le chronomètre disparaît avec la partie

Quand la partie se termine, Partie (et donc Chronometre) est retiré de l'écran : le nettoyage s'exécute, la minuterie s'arrête et le titre de l'onglet est restauré.

📌 Une solution

Ce qu'il faut retenir​

NotionRésumé
EffetCode qui synchronise le composant avec l'extérieur (minuterie, titre, réseau), exécuté après l'affichage.
Dépendances[] une fois, [x] quand x change, absent : à chaque rendu (à éviter).
NettoyageLa fonction renvoyée par l'effet arrête ce qui a démarré.
StrictModeExécute les effets deux fois en développement pour révéler les nettoyages oubliés.
MinuterieUtiliser setX((ancien) => ...) pour éviter la valeur périmée.
À éviterUn effet pour réagir à un clic ou pour calculer une valeur dérivée.

Aperçu de la prochaine séance​

Vous savez lancer et nettoyer un effet. Dans la prochaine séance, l'effet ira chercher des données sur un serveur avec fetch : une opération lente, qui peut échouer, arriver en retard ou être annulée. Vous y appliquerez tout ce que vous avez appris sur l'asynchrone robuste.