Retour au cours

frontend / react

Error Boundaries

Leçon 221 exercice

Explication

Ce que vous allez apprendre

  • Comprendre pourquoi une erreur de rendu non gérée peut faire planter toute l'application
  • Écrire un Error Boundary avec getDerivedStateFromError et componentDidCatch
  • Placer plusieurs boundaries ciblées pour isoler les pannes zone par zone
  • Identifier précisément ce qu'un Error Boundary ne capture jamais
  • Utiliser la librairie react-error-boundary pour un fallback avec reset

Dans quel contexte ?

Un widget météo tiers intégré dans src/components/WeatherWidget.jsx lève occasionnellement une exception quand l'API externe renvoie une réponse malformée. Sans protection, cette erreur fait disparaître toute la page — y compris le tableau de bord principal, totalement indépendant du widget météo — et affiche un écran blanc à l'utilisateur. Envelopper uniquement ce widget dans son propre <ErrorBoundary> confine la panne à ce widget seul, le reste de l'application continuant de fonctionner normalement.

Étape 1 : le problème sans Error Boundary

Par défaut, une erreur JavaScript non gérée levée pendant le rendu d'un composant ne reste pas confinée à ce composant : elle fait s'effondrer tout l'arbre qui l'englobe.

Étape 2 : la conséquence concrète

Un simple bug dans un petit widget secondaire peut ainsi rendre toute l'application inutilisable, jusqu'à afficher une page blanche à l'utilisateur.

Étape 3 : la solution, un Error Boundary

Un Error Boundary est un composant spécial qui intercepte les erreurs survenant pendant le rendu de ses descendants, et affiche à la place une interface de secours.

Étape 4 : pourquoi c'est encore une class component

Fait notable : à ce jour, il n'existe aucun hook équivalent, ce qui reste l'un des rares cas où une class component est réellement nécessaire.

Étape 5 : les deux méthodes qui s'y combinent

getDerivedStateFromError met à jour l'état pour afficher le fallback au rendu suivant. componentDidCatch s'exécute après coup, typiquement pour envoyer l'erreur à un service de suivi.

Étape 6 : ce qu'un Error Boundary ne capture jamais

C'est le point le plus important : il ne capture QUE les erreurs de rendu de ses descendants, jamais celles d'un gestionnaire d'événement ou d'une promesse rejetée dans un effet.

Piège fréquent

Un try/catch classique reste indispensable dans les handlers d'événements et les effets asynchrones : un Error Boundary ne verra jamais une erreur levée dans un onClick ou dans un .catch() de fetch, même si le composant est bien enveloppé.

Type d'erreurCapturée par un Error Boundary ?
Erreur pendant le rendu d'un composant descendantOui
Erreur dans un handler onClick/onChangeNon — try/catch manuel requis
Promesse rejetée dans un useEffectNon — .catch() manuel requis
Erreur dans l'Error Boundary lui-mêmeNon

Une bonne pratique à retenir

Placer plusieurs boundaries ciblées à différents endroits permet d'isoler les pannes zone par zone, pour qu'une erreur dans un widget secondaire n'affecte jamais le reste.

Et ensuite ?

Une fois la robustesse assurée, la prochaine étape revient sur un sujet déjà abordé côté HTML : l'accessibilité, appliquée spécifiquement à React.

Commandes & code

Error Boundaries

Empêcher qu'une erreur dans un composant ne fasse planter TOUTE l'application.

jsx
// Error Boundary : DOIT être une class component -- pas d'équivalent hook natif à ce jour
import { Component } from "react";

class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = { aUneErreur: false, erreur: null };
    }

    // déclenché pendant le rendu -- met à jour le state pour afficher le fallback au prochain rendu
    static getDerivedStateFromError(erreur) {
        return { aUneErreur: true, erreur };
    }

    // déclenché APRÈS le rendu -- effet de bord : logger l'erreur vers un service externe
    componentDidCatch(erreur, infos) {
        console.error("Erreur capturée :", erreur, infos.componentStack);
        envoyerVersSentry(erreur, infos);
    }

    render() {
        if (this.state.aUneErreur) {
            return this.props.fallback ?? <p>Une erreur est survenue.</p>;
        }
        return this.props.children;
    }
}
jsx
// Utilisation : englober des zones précises, pas forcément toute l'app d'un bloc
function App() {
    return (
        <div>
            <Header /> {/* si Header plante, tout le reste de l'app disparaît aussi -- hors boundary */}

            <ErrorBoundary fallback={<PageErreurGenerique />}>
                <TableauDeBord />
            </ErrorBoundary>

            <ErrorBoundary fallback={<p>Widget indisponible</p>}>
                <WidgetMeteoTiers /> {/* un composant tiers instable, isolé, n'affecte pas le reste */}
            </ErrorBoundary>
        </div>
    );
}
jsx
// Ce qu'un Error Boundary NE capture PAS -- limitations importantes à connaître
// 1. Erreurs dans des handlers d'événements (try/catch classique nécessaire)
function Bouton() {
    function gererClic() {
        try {
            operationRisquee();
        } catch (erreur) {
            console.error(erreur); // Error Boundary ne verra JAMAIS cette erreur
        }
    }
    return <button onClick={gererClic}>Cliquer</button>;
}

// 2. Erreurs asynchrones (setTimeout, promesses non attendues dans un effet)
useEffect(() => {
    fetch("/api/donnees").catch((erreur) => {
        // idem : à gérer manuellement, un Error Boundary ne l'attrape pas
        console.error(erreur);
    });
}, []);

// 3. Erreurs côté serveur (SSR) -- nécessitent leur propre gestion selon le framework
// 4. Erreurs DANS l'Error Boundary lui-même
jsx
// react-error-boundary : librairie utilitaire courante, avec reset et hooks
import { ErrorBoundary } from "react-error-boundary";

function FallbackErreur({ error, resetErrorBoundary }) {
    return (
        <div role="alert">
            <p>Erreur : {error.message}</p>
            <button onClick={resetErrorBoundary}>Réessayer</button>
        </div>
    );
}

function App() {
    return (
        <ErrorBoundary
            FallbackComponent={FallbackErreur}
            onReset={() => window.location.reload()}
            onError={(erreur, infos) => envoyerVersSentry(erreur, infos)}
        >
            <TableauDeBord />
        </ErrorBoundary>
    );
}

Résumé

  • Un Error Boundary est nécessairement une class component (getDerivedStateFromError + componentDidCatch).
  • Il capture les erreurs de rendu des descendants, jamais celles des handlers d'événements ou du code asynchrone.
  • Placer plusieurs boundaries ciblées isole les pannes plutôt que de faire disparaître toute l'application.
  • react-error-boundary ajoute un mécanisme de reset et une API en composant fonction, pratique en production.

Exercices pratiques

1 disponible
1

Mission : contenir la panne du widget météo sans y arriver vraiment

Objectif : Diagnostiquer pourquoi un Error Boundary posé au bon endroit ne capture pas certaines erreurs, puis corriger le placement et la gestion manquante.

Contexte

Un développeur enveloppe <WeatherWidget /> dans un <ErrorBoundary> comme le recommande la leçon. Pourtant, deux bugs distincts continuent de faire planter toute l'application : le premier survient dans un onClick du widget (operationRisquee() sans try/catch), le second dans un useEffect du widget qui appelle fetch("/api/meteo").catch(...). Le développeur est convaincu que son ErrorBoundary est mal écrit puisqu'il "ne capture rien".

Résoudre l’exercice →