Cours JavaScript et React · Module 16: Gestion des erreurs et Suspense

react-error-boundary - une bibliothèque moderne

6 min de lecture
Dans cette leçon6

Ta cloison de classe de la leçon précédente fonctionne mais regarde ce qui lui manque : impossible de la réinitialiser après la réparation du module, elle ne réagira pas à une erreur venant d'un clic ou d'un fetch, et chaque projet la réécrit depuis le début. Créer des Error Boundaries à partir de zéro nécessite d'écrire des composants de classe, ce qui, à l'ère des hooks, peut sembler archaïque. Heureusement, il existe la bibliothèque react-error-boundary, qui simplifie grandement le travail avec la gestion des erreurs. Même la documentation react.dev la recommande comme alternative prête à l'emploi à ta propre classe.

Installation et utilisation de base

Tu installes la bibliothèque comme n'importe quel paquet npm. La commande ci-dessous concerne npm, dans un projet avec yarn tu taperas yarn add react-error-boundary :

1npm install react-error-boundary

Au moment de l'écriture, la version actuelle est la 6.x. Elle fonctionne avec React 19 (ainsi que 18), elle est distribuée comme module ES et c'est un composant client, donc dans le Next.js App Router, le fichier qui l'utilise doit avoir la directive "use client".

La bibliothèque fournit un composant ErrorBoundary prêt à l'emploi avec de nombreuses fonctionnalités utiles. Nous définissons d'abord le composant de repli, puis nous enveloppons la station et branchons deux callbacks :

1import { ErrorBoundary } from 'react-error-boundary';
2
3function ErrorFallback({ error, resetErrorBoundary }) {
4  return (
5    <div className="error-panel">
6      <h2>Panne du module !</h2>
7      <p>{error.message}</p>
8      <button onClick={resetErrorBoundary}>
9        Redémarrer le module
10      </button>
11    </div>
12  );
13}
14
15function App() {
16  return (
17    <ErrorBoundary
18      FallbackComponent={ErrorFallback}
19      onError={(error, info) => {
20        // Journaliser vers un service de monitoring
21        console.error('Logged error:', error);
22      }}
23      onReset={() => {
24        // Réinitialiser l'état de l'application après avoir corrigé l'erreur
25        console.log('Error boundary reset');
26      }}
27    >
28      <SpaceStation />
29    </ErrorBoundary>
30  );
31}

onError correspond à la méthode componentDidCatch : il reçoit l'erreur et un objet avec componentStack. onReset sera appelé quand la limite sera réinitialisée, par exemple après un clic sur "Redémarrer le module". React essaiera alors de rendre à nouveau SpaceStation. Une remarque pour la version 6 : dans les types TypeScript, error est de type unknown, car on peut lever n'importe quoi, pas seulement un objet Error. Si tu veux extraire le message en toute sécurité, la bibliothèque exporte la fonction utilitaire getErrorMessage(error).

FallbackComponent vs fallback vs fallbackRender

La bibliothèque propose trois façons de définir l'UI de repli. Elles font la même chose, mais diffèrent par leur confort et leur accès aux données de l'erreur.

1. FallbackComponent (recommandé)

Tu transmets un composant, et la bibliothèque le rend elle-même avec les props error et resetErrorBoundary :

1function MyFallback({ error, resetErrorBoundary }) {
2  return (
3    <div>
4      <p>Erreur : {error.message}</p>
5      <button onClick={resetErrorBoundary}>Réessayer</button>
6    </div>
7  );
8}
9
10<ErrorBoundary FallbackComponent={MyFallback}>
11  <App />
12</ErrorBoundary>

Un tel composant peut être testé séparément et utilisé dans plusieurs limites, c'est pourquoi c'est mon choix par défaut.

2. fallback (JSX simple)

Quand tu n'as besoin ni du contenu de l'erreur ni d'un bouton de réinitialisation, un élément JSX prêt à l'emploi suffit :

1<ErrorBoundary fallback={<p>Quelque chose s'est mal passé !</p>}>
2  <App />
3</ErrorBoundary>

Cette variante ne reçoit aucune prop, donc l'équipage verra le message, mais ne pourra pas redémarrer le module.

3. fallbackRender (render prop)

La troisième option est une fonction appelée avec les mêmes données, pratique pour des fallbacks courts et ponctuels :

1<ErrorBoundary
2  fallbackRender={({ error, resetErrorBoundary }) => (
3    <div>
4      <p>{error.message}</p>
5      <button onClick={resetErrorBoundary}>Reset</button>
6    </div>
7  )}
8>
9  <App />
10</ErrorBoundary>

La fonction reçoit le même objet que FallbackComponent, seule la forme d'écriture change.

Le hook useErrorBoundary

L'un des outils les plus puissants de la bibliothèque - le hook useErrorBoundary te permet de déclencher par programmation des erreurs dans une Error Boundary depuis des gestionnaires d'événements et du code asynchrone ! Il retourne la fonction showBoundary(error), qui transmet l'erreur à la limite la plus proche, ainsi que resetBoundary(), qui la réinitialise :

1import { useErrorBoundary } from 'react-error-boundary';
2
3function DataFetcher() {
4  const { showBoundary } = useErrorBoundary();
5  const [data, setData] = useState(null);
6
7  const fetchData = async () => {
8    try {
9      const response = await fetch('/api/missions');
10      if (!response.ok) {
11        throw new Error('Erreur lors de la récupération des données de mission');
12      }
13      const result = await response.json();
14      setData(result);
15    } catch (error) {
16      // Transmets l'erreur à l'Error Boundary la plus proche !
17      showBoundary(error);
18    }
19  };
20
21  return (
22    <div>
23      <button onClick={fetchData}>Récupérer les données de mission</button>
24      {data && <p>Missions : {data.length}</p>}
25    </div>
26  );
27}

Le try/catch attrape toujours l'erreur, seul ce que nous en faisons change : au lieu d'un message local, nous déclenchons le même fallback que celui qui gère les erreurs de rendu. Dans React 19, il existe aussi une alternative : une erreur levée dans la fonction passée à startTransition de useTransition arrive elle aussi à la limite la plus proche.

resetKeys - réinitialisation automatique

Tu peux configurer resetKeys - une liste de valeurs dont le changement réinitialisera automatiquement l'Error Boundary. C'est utile quand la panne dépend des données sélectionnées :

1function App() {
2  const [selectedPlanet, setSelectedPlanet] = useState('Mars');
3
4  return (
5    <ErrorBoundary
6      FallbackComponent={ErrorFallback}
7      resetKeys={[selectedPlanet]}
8      onReset={() => {
9        // Nettoyage après réinitialisation
10        console.log('Boundary reset - nouvelle planète :', selectedPlanet);
11      }}
12    >
13      <PlanetDetails planet={selectedPlanet} />
14    </ErrorBoundary>
15  );
16}
17// Quand l'utilisateur change de planète, l'Error Boundary se réinitialise automatiquement

Bon à savoir : après un changement de clé, onReset reçoit un objet avec le champ reason: 'keys', et lors d'une réinitialisation par bouton reason: 'imperative-api'. Grâce à cela, tu sais ce qui a déclenché le redémarrage.

Error Boundaries imbriquées

Tu peux imbriquer plusieurs Error Boundaries pour différents niveaux de gestion, comme dans la leçon précédente :

1<ErrorBoundary FallbackComponent={AppCrashScreen}>
2  <Header />
3  <ErrorBoundary FallbackComponent={SectionError}>
4    <MissionControl />
5  </ErrorBoundary>
6  <ErrorBoundary FallbackComponent={SectionError}>
7    <CrewPanel />
8  </ErrorBoundary>
9</ErrorBoundary>

Si une erreur survient dans MissionControl, elle sera attrapée par l'ErrorBoundary interne. Si l'interne lève elle-même une erreur, celle-ci sera attrapée par l'externe.

Comparaison : Error Boundary personnalisée vs react-error-boundary

Mettons les deux cloisons côte à côte :

FonctionnalitéPersonnalisée (classe)react-error-boundary
RéinitialisationImplémentée manuellementIntégrée (resetErrorBoundary)
Réinit. autoAucuneresetKeys
HookAucunuseErrorBoundary
JournalisationManuelleIntégrée (onError)
Erreurs asyncN'attrape passhowBoundary()
BoilerplateBeaucoupMinimal

L'avantage clé de la bibliothèque, c'est le hook useErrorBoundary - il permet d'attraper les erreurs des gestionnaires d'événements et du code asynchrone, ce que les Error Boundaries natives ne savent pas faire. En pratique, si tu construis une application de production sérieuse, react-error-boundary est le standard de l'industrie - tout comme les systèmes de sécurité normalisés sur les vaisseaux spatiaux. En dessous, c'est toujours la même classe avec getDerivedStateFromError, donc tout ce que tu as appris auparavant reste valable. Dans les prochaines leçons, tu la combineras avec les formulaires, le monitoring et Suspense.

Souviens-toi : ne construis pas une cloison à partir de zéro sur chaque vaisseau, prends un module éprouvé et concentre-toi sur ce que verra l'équipage.

Code de cette leçon : App.jsx
1import React, { useState } from 'react';
2import './styles.css';
3
4// Simulation de react-error-boundary
5// (en production : import { ErrorBoundary } from 'react-error-boundary')
6
7class ErrorBoundary extends React.Component {
8  constructor(props) {
9    super(props);
10    this.state = { hasError: false, error: null };
11  }
12
13  static getDerivedStateFromError(error) {
14    return { hasError: true, error };
15  }
16
17  componentDidCatch(error, info) {
18    if (this.props.onError) {
19      this.props.onError(error, info);
20    }
21  }
22
23  resetErrorBoundary = () => {
24    if (this.props.onReset) {
25      this.props.onReset();
26    }
27    this.setState({ hasError: false, error: null });
28  };
29
30  render() {
31    if (this.state.hasError) {
32      const { FallbackComponent, fallback, fallbackRender } = this.props;
33      if (FallbackComponent) {
34        return (
35          <FallbackComponent
36            error={this.state.error}
37            resetErrorBoundary={this.resetErrorBoundary}
38          />
39        );
40      }
41      if (fallbackRender) {
42        return fallbackRender({
43          error: this.state.error,
44          resetErrorBoundary: this.resetErrorBoundary,
45        });
46      }
47      if (fallback) return fallback;
48      return <p>Quelque chose s'est mal passé</p>;
49    }
50    return this.props.children;
51  }
52}
53
54// FallbackComponent
55function ModuleError({ error, resetErrorBoundary }) {
56  return (
57    <div className="error-card">
58      <h3>Panne du module</h3>
59      <p className="error-msg">{error.message}</p>
60      <button onClick={resetErrorBoundary} className="retry-btn">
61        Redémarrer le module
62      </button>
63    </div>
64  );
65}
66
67// Composant instable
68function UnstablePanel({ name }) {
69  if (Math.random() < 0.5) {
70    throw new Error(name + ' : erreur critique du système !');
71  }
72  return (
73    <div className="panel-ok">
74      <span className="dot green" />
75      {name} - En ligne
76    </div>
77  );
78}
79
80function StablePanel({ name }) {
81  return (
82    <div className="panel-ok">
83      <span className="dot green" />
84      {name} - En ligne
85    </div>
86  );
87}
88
89export default function App() {
90  const [key, setKey] = useState(0);
91
92  return (
93    <div className="app">
94      <h1>react-error-boundary</h1>
95      <p className="subtitle">
96        Trois façons de définir l'UI de repli
97      </p>
98      <button onClick={() => setKey(k => k + 1)} className="reset-all">
99        Tout réinitialiser
100      </button>
101
102      <div className="panels" key={key}>
103        <div className="panel-section">
104          <h2>FallbackComponent</h2>
105          <ErrorBoundary
106            FallbackComponent={ModuleError}
107            onError={(err) => console.log('Journalisé :', err.message)}
108            onReset={() => console.log('Réinitialisé !')}
109          >
110            <UnstablePanel name="Navigation" />
111          </ErrorBoundary>
112        </div>
113
114        <div className="panel-section">
115          <h2>fallbackRender</h2>
116          <ErrorBoundary
117            fallbackRender={({ error, resetErrorBoundary }) => (
118              <div className="error-card">
119                <p>{error.message}</p>
120                <button onClick={resetErrorBoundary} className="retry-btn">
121                  Réparer
122                </button>
123              </div>
124            )}
125          >
126            <UnstablePanel name="Communication" />
127          </ErrorBoundary>
128        </div>
129
130        <div className="panel-section">
131          <h2>fallback (JSX simple)</h2>
132          <ErrorBoundary fallback={<p className="simple-err">Module hors ligne</p>}>
133            <UnstablePanel name="Capteurs" />
134          </ErrorBoundary>
135        </div>
136
137        <div className="panel-section">
138          <h2>Module stable</h2>
139          <ErrorBoundary FallbackComponent={ModuleError}>
140            <StablePanel name="Survie" />
141          </ErrorBoundary>
142        </div>
143      </div>
144    </div>
145  );
146}

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quelles props reçoit FallbackComponent dans react-error-boundary ?

  2. 2. À quoi sert le hook useErrorBoundary de la bibliothèque react-error-boundary ?

Tâches pratiques dans le jeu

  • Tri horizontal

    Ordonne la syntaxe correcte pour utiliser ErrorBoundary avec FallbackComponent :

  • Éditeur de code

    Implémente la gestion des erreurs async

  • Clique dans l'ordre

    Clique les éléments dans l'ordre pour la syntaxe d'ErrorBoundary avec réinitialisation :

  • Clique dans l'ordre

    Clique les éléments dans l'ordre pour utiliser useErrorBoundary avec fetch :

Articles utiles