Apprends à gérer gracieusement les erreurs d'exécution dans React en utilisant les error boundaries — des composants de classe qui capturent les erreurs JavaScript dans leur arbre de composants enfants et affichent une interface de secours.
Dans une application JavaScript normale, une seule erreur non gérée peut faire planter toute la page. Les applications React font face au même problème — si un composant génère une erreur pendant le rendu, tout l'arbre de composants se démonte, laissant l'utilisateur avec un écran blanc.
Les error boundaries résolvent ce problème en capturant les erreurs JavaScript n'importe où dans leur arbre de composants enfants, en journalisant ces erreurs et en affichant une interface de secours au lieu de faire planter toute l'application.
Pense aux error boundaries comme à un bloc try...catch, mais pour les composants React. Tout comme tu enveloppes du code risqué dans try/catch pour empêcher ton programme de planter, tu enveloppes des composants risqués dans une error boundary pour empêcher ton interface de devenir blanche.
Points clés sur les error boundaries :
Les error boundaries sont l'un des rares cas d'utilisation restants pour les composants de classe dans React moderne. Jusqu'à React 19, il n'existe toujours pas d'équivalent hook pour capturer les erreurs de rendu. L'équipe React a discuté d'en ajouter un, mais il n'a pas encore été livré.
Un composant de classe devient une error boundary lorsqu'il définit une ou les deux méthodes de cycle de vie suivantes :
static getDerivedStateFromError(error)
static getDerivedStateFromError(error) {
// Met à jour l'état pour que le prochain rendu affiche l'interface de secours
return { hasError: true };
}componentDidCatch(error, errorInfo)
componentStack montrant quel composant dans l'arbre a généré l'erreur.componentDidCatch(error, errorInfo) {
// Journalise l'erreur vers un service de rapport d'erreurs
console.error('Error caught:', error);
console.error('Component stack:', errorInfo.componentStack);
}Tu utilises généralement getDerivedStateFromError pour afficher l'interface de secours et componentDidCatch pour journaliser l'erreur. Les deux méthodes peuvent être utilisées ensemble dans le même composant.
Une error boundary bien conçue est réutilisable dans toute ton application. Voici un pattern complet :
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
// Tu peux afficher n'importe quelle interface de secours personnalisée
return this.props.fallback || <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}Utilisation :
<ErrorBoundary fallback={<p>Oops! This section failed to load.</p>}>
<RiskyComponent />
</ErrorBoundary>Remarque la prop fallback — elle te permet de personnaliser le message d'erreur pour chaque utilisation sans modifier l'error boundary elle-même. Le pattern this.props.children affiche les composants enfants normalement quand il n'y a pas d'erreur.
Certaines bibliothèques, comme react-error-boundary, fournissent un composant prêt à l'emploi avec plus de fonctionnalités comme la récupération d'erreur et la réinitialisation. Mais comprendre comment en construire une à partir de zéro est essentiel parce que :
Afficher une interface de secours n'est que la moitié de la solution — tu veux aussi donner aux utilisateurs un moyen de récupérer de l'erreur. Les stratégies courantes incluent :
1. Bouton de nouvelle tentative :
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
handleReset = () => {
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
return (
<div>
<p>Something went wrong.</p>
<button onClick={this.handleReset}>Try Again</button>
</div>
);
}
return this.props.children;
}
}Quand l'utilisateur clique sur "Try Again", l'état se réinitialise et React tente de refaire le rendu des enfants. Si l'erreur était transitoire (comme un problème réseau), le nouveau rendu peut réussir.
2. Réinitialisation basée sur la clé :
Changer la prop key sur une error boundary force React à la démonter et la remonter, réinitialisant son état :
<ErrorBoundary key={resetKey}>
<DataComponent />
</ErrorBoundary>3. Naviguer ailleurs : Dans une application avec routage, tu peux rediriger l'utilisateur vers une page sûre quand une erreur se produit.
Important : Si le bug sous-jacent n'est pas corrigé, le composant générera la même erreur à nouveau lors du nouveau rendu. Les stratégies de récupération fonctionnent mieux pour les erreurs transitoires (échecs réseau, conditions de course) plutôt que pour les bugs de logique.
L'endroit où tu places les error boundaries est important. Tu ne devrais pas envelopper toute ton application dans une seule error boundary — c'est comme capturer chaque exception au niveau supérieur. Utilise plutôt une approche en couches :
1. Boundary de niveau supérieur — Capture les erreurs vraiment inattendues et affiche un écran d'erreur pleine page :
<ErrorBoundary fallback={<FullPageError />}>
<App />
</ErrorBoundary>2. Boundaries au niveau des routes — Isole les erreurs aux pages individuelles pour que la navigation fonctionne toujours :
<Route path="/dashboard">
<ErrorBoundary fallback={<p>Dashboard failed to load.</p>}>
<Dashboard />
</ErrorBoundary>
</Route>3. Boundaries au niveau des fonctionnalités — Protège des widgets spécifiques pour que le reste de la page reste fonctionnel :
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<ErrorBoundary fallback={<p>Chart unavailable.</p>}>
<AnalyticsChart />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Feed unavailable.</p>}>
<ActivityFeed />
</ErrorBoundary>
</div>
);
}De cette façon, si le graphique d'analyse plante, le fil d'activité et le reste du tableau de bord restent visibles.
Limitations à retenir :
.catch() ou try/catch dans les fonctions async.<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Caught by ErrorBoundary:", error);
}
render() {
if (this.state.hasError) {
return <div id="error">Something went wrong</div>;
}
return this.props.children;
}
}
function BuggyComponent() {
throw new Error("I crashed!");
return <p>This will never render</p>;
}
function SafeComponent() {
return <p>I am working fine!</p>;
}
function App() {
return (
<div>
<h1>Error Boundary Demo</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
<SafeComponent />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>Pourquoi les error boundaries doivent-elles être des composants de classe dans React ?