Aller au contenu

React : état et effets

import { useState } from 'react';
function Compteur() {
const [n, setN] = useState(0);
return (
<>
<p>{n}</p>
<button onClick={() => setN(n + 1)}>+1</button>
<button onClick={() => setN((precedent) => precedent + 1)}>
+1 (forme fonctionnelle)
</button>
</>
);
}
  • Modifier l’état uniquement par la fonction set… ; jamais par mutation directe.
  • Pour une valeur qui dépend de la précédente, utiliser la forme fonctionnelle (setN(p => p + 1)).
  • Objets et tableaux : en créer de nouveaux ({ ...ancien, champ: 1 }, [...liste, element]).
const [utilisateur, setUtilisateur] = useState({ nom: 'Alice', age: 30 });
setUtilisateur({ ...utilisateur, age: 31 });
const [taches, setTaches] = useState<string[]>([]);
setTaches([...taches, 'Nouvelle tâche']);
setTaches(taches.filter((t) => t !== 'Ancienne'));
  • Dans le composant qui en a besoin, ou dans leur parent commun le plus proche (« remonter l’état »).
  • Ne pas stocker ce qui peut se calculer : const total = lignes.reduce(…) dans le rendu plutôt qu’un état total.
  • Ne pas dupliquer une donnée dans plusieurs états.

Pour un état complexe ou plusieurs actions liées :

import { useReducer } from 'react';
type Action =
{ type: 'ajouter'; texte: string } | { type: 'supprimer'; id: number };
type Tache = { id: number; texte: string };
function reducteur(etat: Tache[], action: Action): Tache[] {
switch (action.type) {
case 'ajouter':
return [...etat, { id: Date.now(), texte: action.texte }];
case 'supprimer':
return etat.filter((t) => t.id !== action.id);
}
}
function Taches() {
const [taches, dispatch] = useReducer(reducteur, []);
return (
<>
<button onClick={() => dispatch({ type: 'ajouter', texte: 'À faire' })}>
Ajouter
</button>
<ul>
{taches.map((t) => (
<li
key={t.id}
onClick={() => dispatch({ type: 'supprimer', id: t.id })}
>
{t.texte}
</li>
))}
</ul>
</>
);
}
import { useRef } from 'react';
function Champ() {
const entree = useRef<HTMLInputElement>(null);
return (
<>
<input ref={entree} />
<button onClick={() => entree.current?.focus()}>Focus</button>
</>
);
}

useRef conserve une valeur sans déclencher de rendu quand elle change : accès au DOM, identifiant de minuteur, valeur précédente.

Un effet synchronise le composant avec un système extérieur (réseau, minuteur, abonnement, titre du document) après le rendu.

import { useEffect, useState } from 'react';
function Horloge() {
const [heure, setHeure] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setHeure(new Date()), 1000);
return () => clearInterval(id); // nettoyage
}, []); // tableau de dépendances
return <p>{heure.toLocaleTimeString('fr-FR')}</p>;
}
Tableau de dépendances L’effet s’exécute
Absent Après chaque rendu
[] Une fois, au montage
[a, b] Au montage, puis quand a ou b change

Règles :

  • Lister toutes les valeurs utilisées dans l’effet (le plugin ESLint react-hooks le vérifie).
  • Nettoyer : annuler minuteurs, abonnements et requêtes dans la fonction renvoyée.
  • Ne pas utiliser un effet pour calculer une valeur affichable ni pour réagir à un clic (placer cette logique dans le gestionnaire d’événement).
useEffect(() => {
document.title = `${n} notifications`;
}, [n]);

En développement, StrictMode exécute chaque effet deux fois (montage, démontage, montage) pour détecter les nettoyages manquants.

Partager une valeur avec toute une branche de l’arbre, sans la passer par chaque niveau :

import { createContext, useContext, useState, type ReactNode } from 'react';
type Theme = 'light' | 'dark';
const ContexteTheme = createContext<{
theme: Theme;
basculer: () => void;
} | null>(null);
export function FournisseurTheme({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('light');
const basculer = () => setTheme((t) => (t === 'light' ? 'dark' : 'light'));
return (
<ContexteTheme.Provider value={{ theme, basculer }}>
{children}
</ContexteTheme.Provider>
);
}
export function useTheme() {
const contexte = useContext(ContexteTheme);
if (!contexte)
throw new Error('useTheme doit être utilisé dans FournisseurTheme');
return contexte;
}

Réserver le contexte aux données globales et peu changeantes (thème, utilisateur connecté, langue) : chaque changement de la valeur refait le rendu de tous les consommateurs.

Réutiliser une logique d’état :

import { useEffect, useState } from 'react';
export function useStockageLocal<T>(cle: string, valeurInitiale: T) {
const [valeur, setValeur] = useState<T>(() => {
try {
const enregistre = localStorage.getItem(cle);
return enregistre ? (JSON.parse(enregistre) as T) : valeurInitiale;
} catch {
return valeurInitiale;
}
});
useEffect(() => {
try {
localStorage.setItem(cle, JSON.stringify(valeur));
} catch {
/* stockage indisponible : ignoré */
}
}, [cle, valeur]);
return [valeur, setValeur] as const;
}

Le nom d’un hook commence toujours par use. Les hooks s’appellent au niveau supérieur d’un composant ou d’un autre hook (jamais dans une condition, une boucle ou une fonction imbriquée).

Symptôme Piste
Too many re-renders set… appelé pendant le rendu : le placer dans un gestionnaire ou un effet
Boucle infinie dans useEffect L’effet modifie une valeur de ses dépendances ; objet ou fonction recréés à chaque rendu
L’état ne se met pas à jour comme prévu Valeur lue avant la mise à jour : forme fonctionnelle de set…
Mutation directe sans rendu (liste.push(x)) Créer un nouveau tableau
Rendered more hooks than during the previous render Hook appelé dans une condition
Effet exécuté deux fois en développement StrictMode : ajouter le nettoyage
Can't perform a React state update on an unmounted component Requête ou minuteur non annulé : nettoyage

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Aucune.