Apprenez à utiliser les ErrorBoundaries de React pour gérer les erreurs avec élégance, éviter les plantages d'application et offrir une meilleure expérience utilisateur.
ErrorBoundary React : Stratégies d'isolation et de récupération des erreurs
Dans le monde dynamique du développement front-end, surtout lorsqu'on travaille avec des frameworks complexes basés sur des composants comme React, les erreurs inattendues sont inévitables. Ces erreurs, si elles ne sont pas gérées correctement, peuvent entraîner des plantages d'application et une expérience utilisateur frustrante. Le composant ErrorBoundary de React offre une solution robuste pour gérer ces erreurs avec élégance, les isoler et fournir des stratégies de récupération. Ce guide complet explore la puissance d'ErrorBoundary, en démontrant comment l'implémenter efficacement pour construire des applications React plus résilientes et conviviales pour un public mondial.
Comprendre la nécessité des Error Boundaries
Avant de plonger dans l'implémentation, comprenons pourquoi les error boundaries sont essentiels. Dans React, les erreurs qui se produisent pendant le rendu, dans les méthodes de cycle de vie ou dans les constructeurs des composants enfants peuvent potentiellement faire planter toute l'application. C'est parce que les erreurs non interceptées se propagent vers le haut de l'arborescence des composants, conduisant souvent à un écran blanc ou à un message d'erreur inutile. Imaginez un utilisateur au Japon essayant de réaliser une transaction financière importante, pour finalement se retrouver face à un écran blanc à cause d'une erreur mineure dans un composant apparemment sans rapport. Cela illustre le besoin critique d'une gestion proactive des erreurs.
Les error boundaries offrent un moyen d'intercepter les erreurs JavaScript n'importe où dans l'arborescence de leurs composants enfants, de journaliser ces erreurs et d'afficher une interface utilisateur de secours au lieu de faire planter l'arborescence des composants. Ils vous permettent d'isoler les composants défectueux et d'empêcher les erreurs dans une partie de votre application d'affecter les autres, garantissant une expérience utilisateur plus stable et fiable à l'échelle mondiale.
Qu'est-ce qu'un ErrorBoundary React ?
Un ErrorBoundary est un composant React qui intercepte les erreurs JavaScript n'importe où dans l'arborescence de ses composants enfants, journalise ces erreurs et affiche une interface utilisateur de secours. C'est un composant de classe qui implémente l'une ou les deux méthodes de cycle de vie suivantes :
static getDerivedStateFromError(error): Cette méthode de cycle de vie est invoquée après qu'une erreur a été levée par un composant descendant. Elle reçoit l'erreur qui a été levée en argument et doit retourner une valeur pour mettre à jour l'état du composant.componentDidCatch(error, info): Cette méthode de cycle de vie est invoquée après qu'une erreur a été levée par un composant descendant. Elle reçoit deux arguments : l'erreur qui a été levée et un objet info contenant des informations sur le composant qui a levé l'erreur. Vous pouvez utiliser cette méthode pour journaliser les informations d'erreur ou effectuer d'autres effets de bord.
Créer un composant ErrorBoundary de base
Créons un composant ErrorBoundary de base pour illustrer les principes fondamentaux.
Exemple de code
Voici le code pour un composant ErrorBoundary simple :
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error) {
// Mettre à jour l'état pour que le prochain rendu affiche l'UI de secours.
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// Exemple de "componentStack" :
// dans ComponentThatThrows (créé par App)
// dans App
console.error("Caught an error:", error);
console.error("Error info:", info.componentStack);
this.setState({ error: error, errorInfo: info });
// Vous pouvez également journaliser l'erreur dans un service de reporting d'erreurs
// logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Vous pouvez afficher n'importe quelle UI de secours personnalisée
return (
Quelque chose s'est mal passé.
Erreur : {this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.componentStack}
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Explication
- Constructeur : Le constructeur initialise l'état du composant avec
hasErrordéfini surfalse. Nous stockons également l'erreur et les informations sur l'erreur à des fins de débogage. getDerivedStateFromError(error): Cette méthode statique est invoquée lorsqu'une erreur est levée par un composant enfant. Elle met à jour l'état pour indiquer qu'une erreur s'est produite.componentDidCatch(error, info): Cette méthode est invoquée après qu'une erreur a été levée. Elle reçoit l'erreur et un objetinfocontenant des informations sur la pile de composants. Ici, nous journalisons l'erreur dans la console (remplacez par votre mécanisme de journalisation préféré, tel que Sentry, Bugsnag ou une solution interne personnalisée). Nous définissons également l'erreur et les informations sur l'erreur dans l'état.render(): La méthode de rendu vérifie l'étathasError. Si c'esttrue, elle affiche une interface utilisateur de secours ; sinon, elle affiche les enfants du composant. L'interface de secours doit être informative et conviviale. Inclure les détails de l'erreur et la pile de composants, bien qu'utile pour les développeurs, devrait être rendu de manière conditionnelle ou supprimé dans les environnements de production pour des raisons de sécurité.
Utiliser le composant ErrorBoundary
Pour utiliser le composant ErrorBoundary, il suffit d'envelopper tout composant susceptible de lever une erreur avec celui-ci.
Exemple de code
import ErrorBoundary from './ErrorBoundary';
function MyComponent() {
return (
{/* Composants susceptibles de lever une erreur */}
);
}
function App() {
return (
);
}
export default App;
Explication
Dans cet exemple, MyComponent est enveloppé par ErrorBoundary. Si une erreur se produit dans MyComponent ou ses enfants, ErrorBoundary l'interceptera et affichera l'interface utilisateur de secours.
Stratégies avancées d'ErrorBoundary
Bien que l'ErrorBoundary de base fournisse un niveau fondamental de gestion des erreurs, il existe plusieurs stratégies avancées que vous pouvez mettre en œuvre pour améliorer votre gestion des erreurs.
1. Error Boundaries granulaires
Au lieu d'envelopper toute l'application avec un seul ErrorBoundary, envisagez d'utiliser des error boundaries granulaires. Cela implique de placer des composants ErrorBoundary autour de parties spécifiques de votre application qui sont plus sujettes aux erreurs ou dont la défaillance aurait un impact limité. Par exemple, vous pourriez envelopper des widgets individuels ou des composants qui dépendent de sources de données externes.
Exemple
function ProductList() {
return (
{/* Liste de produits */}
);
}
function RecommendationWidget() {
return (
{/* Moteur de recommandation */}
);
}
function App() {
return (
);
}
Dans cet exemple, le RecommendationWidget a son propre ErrorBoundary. Si le moteur de recommandation tombe en panne, cela n'affectera pas la ProductList, et l'utilisateur pourra toujours parcourir les produits. Cette approche granulaire améliore l'expérience utilisateur globale en isolant les erreurs et en les empêchant de se propager en cascade à travers l'application.
2. Journalisation et rapport d'erreurs
La journalisation des erreurs est cruciale pour le débogage et l'identification des problèmes récurrents. La méthode de cycle de vie componentDidCatch est l'endroit idéal pour s'intégrer à des services de journalisation d'erreurs comme Sentry, Bugsnag ou Rollbar. Ces services fournissent des rapports d'erreurs détaillés, y compris des traces de pile, le contexte utilisateur et des informations sur l'environnement, vous permettant de diagnostiquer et de résoudre rapidement les problèmes. Pensez à anonymiser ou à caviarder les données utilisateur sensibles avant d'envoyer des journaux d'erreurs pour garantir la conformité avec les réglementations sur la confidentialité comme le RGPD.
Exemple
import * as Sentry from "@sentry/react";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(error) {
// Mettre à jour l'état pour que le prochain rendu affiche l'UI de secours.
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// Journaliser l'erreur sur Sentry
Sentry.captureException(error, { extra: info });
// Vous pouvez également journaliser l'erreur dans un service de reporting d'erreurs
console.error("Caught an error:", error);
}
render() {
if (this.state.hasError) {
// Vous pouvez afficher n'importe quelle UI de secours personnalisée
return (
Quelque chose s'est mal passé.
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Dans cet exemple, la méthode componentDidCatch utilise Sentry.captureException pour signaler l'erreur à Sentry. Vous pouvez configurer Sentry pour envoyer des notifications à votre équipe, vous permettant de réagir rapidement aux erreurs critiques.
3. UI de secours personnalisée
L'interface utilisateur de secours affichée par l'ErrorBoundary est une opportunité de fournir une expérience conviviale même lorsque des erreurs se produisent. Au lieu d'afficher un message d'erreur générique, envisagez d'afficher un message plus informatif qui guide l'utilisateur vers une solution. Cela pourrait inclure des instructions sur la façon de rafraîchir la page, de contacter le support ou de réessayer plus tard. Vous pouvez également adapter l'interface de secours en fonction du type d'erreur survenu.
Exemple
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error) {
// Mettre à jour l'état pour que le prochain rendu affiche l'UI de secours.
return {
hasError: true,
error: error,
};
}
componentDidCatch(error, info) {
console.error("Caught an error:", error);
// Vous pouvez également journaliser l'erreur dans un service de reporting d'erreurs
// logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Vous pouvez afficher n'importe quelle UI de secours personnalisée
if (this.state.error instanceof NetworkError) {
return (
Erreur réseau
Veuillez vérifier votre connexion internet et réessayer.
);
} else {
return (
Quelque chose s'est mal passé.
Veuillez essayer de rafraîchir la page ou de contacter le support.
);
}
}
return this.props.children;
}
}
export default ErrorBoundary;
Dans cet exemple, l'interface utilisateur de secours vérifie si l'erreur est une NetworkError. Si c'est le cas, elle affiche un message spécifique demandant à l'utilisateur de vérifier sa connexion Internet. Sinon, elle affiche un message d'erreur générique. Fournir des conseils spécifiques et exploitables peut grandement améliorer l'expérience utilisateur.
4. Mécanismes de nouvelle tentative
Dans certains cas, les erreurs sont transitoires et peuvent être résolues en réessayant l'opération. Vous pouvez implémenter un mécanisme de nouvelle tentative au sein de l'ErrorBoundary pour réessayer automatiquement l'opération échouée après un certain délai. Cela peut être particulièrement utile pour gérer les erreurs réseau ou les pannes de serveur temporaires. Soyez prudent lors de l'implémentation de mécanismes de nouvelle tentative pour des opérations qui pourraient avoir des effets de bord, car les réessayer pourrait entraîner des conséquences imprévues.
Exemple
import React, { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [retryCount, setRetryCount] = useState(0);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`Erreur HTTP ! statut : ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
} catch (e) {
setError(e);
setRetryCount(prevCount => prevCount + 1);
} finally {
setIsLoading(false);
}
};
if (error && retryCount < 3) {
const retryDelay = Math.pow(2, retryCount) * 1000; // Backoff exponentiel
console.log(`Nouvelle tentative dans ${retryDelay / 1000} secondes...`);
const timer = setTimeout(fetchData, retryDelay);
return () => clearTimeout(timer); // Nettoyer le timer au démontage ou au nouveau rendu
}
if (!data) {
fetchData();
}
}, [error, retryCount, data]);
if (isLoading) {
return Chargement des données...
;
}
if (error) {
return Erreur : {error.message} - Réessayé {retryCount} fois.
;
}
return Données : {JSON.stringify(data)}
;
}
function App() {
return (
);
}
export default App;
Dans cet exemple, le DataFetchingComponent tente de récupérer des données d'une API. Si une erreur se produit, il incrémente le retryCount et réessaye l'opération après un délai qui augmente de manière exponentielle. L'ErrorBoundary intercepte toutes les exceptions non gérées et affiche un message d'erreur, incluant le nombre de tentatives.
5. Error Boundaries et Rendu Côté Serveur (SSR)
Lors de l'utilisation du rendu côté serveur (SSR), la gestion des erreurs devient encore plus critique. Les erreurs qui se produisent pendant le processus de rendu côté serveur peuvent faire planter tout le serveur, entraînant des temps d'arrêt et une mauvaise expérience utilisateur. Vous devez vous assurer que vos error boundaries sont correctement configurés pour intercepter les erreurs à la fois sur le serveur et sur le client. Souvent, les frameworks SSR comme Next.js et Remix ont leurs propres mécanismes de gestion des erreurs intégrés qui complètent les Error Boundaries de React.
6. Tester les Error Boundaries
Tester les error boundaries est essentiel pour s'assurer qu'ils fonctionnent correctement et fournissent l'interface utilisateur de secours attendue. Utilisez des bibliothèques de test comme Jest et React Testing Library pour simuler des conditions d'erreur et vérifier que vos error boundaries interceptent les erreurs et affichent l'interface de secours appropriée. Envisagez de tester différents types d'erreurs et de cas limites pour vous assurer que vos error boundaries sont robustes et gèrent un large éventail de scénarios.
Exemple
import { render, screen } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function ComponentThatThrows() {
throw new Error('Ce composant lève une erreur');
return Ceci ne devrait pas être affiché
;
}
test('affiche l\'interface de secours lorsqu\'une erreur est levée', () => {
render(
);
const errorMessage = screen.getByText(/Quelque chose s'est mal passé/i);
expect(errorMessage).toBeInTheDocument();
});
Ce test effectue le rendu d'un composant qui lève une erreur au sein d'un ErrorBoundary. Il vérifie ensuite que l'interface utilisateur de secours est correctement rendue en s'assurant que le message d'erreur est présent dans le document.
7. Dégradation progressive
Les error boundaries sont un composant clé de l'implémentation de la dégradation progressive dans vos applications React. La dégradation progressive est la pratique de concevoir votre application pour qu'elle continue de fonctionner, bien qu'avec des fonctionnalités réduites, même lorsque certaines de ses parties échouent. Les error boundaries vous permettent d'isoler les composants défaillants et de les empêcher d'affecter le reste de l'application. En fournissant une interface utilisateur de secours et des fonctionnalités alternatives, vous pouvez vous assurer que les utilisateurs peuvent toujours accéder aux fonctionnalités essentielles même lorsque des erreurs se produisent.
Pièges courants à éviter
Bien que ErrorBoundary soit un outil puissant, il y a quelques pièges courants à éviter :
- Ne pas envelopper le code asynchrone :
ErrorBoundaryn'intercepte que les erreurs pendant le rendu, dans les méthodes de cycle de vie et dans les constructeurs. Les erreurs dans le code asynchrone (par ex.,setTimeout,Promises) doivent être interceptées à l'aide de blocstry...catchet gérées de manière appropriée au sein de la fonction asynchrone. - Surutiliser les Error Boundaries : Évitez d'envelopper de grandes parties de votre application dans un seul
ErrorBoundary. Cela peut rendre difficile l'isolation de la source des erreurs et peut conduire à ce qu'une interface utilisateur de secours générique soit affichée trop souvent. Utilisez des error boundaries granulaires pour isoler des composants ou des fonctionnalités spécifiques. - Ignorer les informations d'erreur : Ne vous contentez pas d'intercepter les erreurs et d'afficher une interface de secours. Assurez-vous de journaliser les informations d'erreur (y compris la pile de composants) dans un service de reporting d'erreurs ou dans votre console. Cela vous aidera à diagnostiquer et à corriger les problèmes sous-jacents.
- Afficher des informations sensibles en production : Évitez d'afficher des informations d'erreur détaillées (par ex., les traces de pile) dans les environnements de production. Cela peut exposer des informations sensibles aux utilisateurs et peut constituer un risque pour la sécurité. Affichez plutôt un message d'erreur convivial et journalisez les informations détaillées dans un service de reporting d'erreurs.
Error Boundaries avec les composants fonctionnels et les Hooks
Bien que les Error Boundaries soient implémentés en tant que composants de classe, vous pouvez toujours les utiliser efficacement pour gérer les erreurs au sein de composants fonctionnels qui utilisent des hooks. L'approche typique consiste à envelopper le composant fonctionnel dans un composant ErrorBoundary, comme démontré précédemment. La logique de gestion des erreurs réside dans l'ErrorBoundary, isolant efficacement les erreurs qui pourraient se produire pendant le rendu du composant fonctionnel ou l'exécution des hooks.
Plus précisément, toute erreur levée pendant le rendu du composant fonctionnel ou dans le corps d'un hook useEffect sera interceptée par l'ErrorBoundary. Cependant, il est important de noter que les ErrorBoundaries n'interceptent pas les erreurs qui se produisent dans les gestionnaires d'événements (par ex., onClick, onChange) attachés aux éléments DOM au sein du composant fonctionnel. Pour les gestionnaires d'événements, vous devez continuer à utiliser les blocs try...catch traditionnels pour la gestion des erreurs.
Internationalisation et localisation des messages d'erreur
Lors du développement d'applications pour un public mondial, il est crucial d'internationaliser et de localiser vos messages d'erreur. Les messages d'erreur affichés dans l'interface de secours de l'ErrorBoundary doivent être traduits dans la langue préférée de l'utilisateur pour offrir une meilleure expérience utilisateur. Vous pouvez utiliser des bibliothèques comme i18next ou React Intl pour gérer vos traductions et afficher dynamiquement le message d'erreur approprié en fonction de la locale de l'utilisateur.
Exemple avec i18next
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
i18next.init({
resources: {
en: {
translation: {
'error.generic': 'Something went wrong. Please try again later.',
'error.network': 'Network error. Please check your internet connection.',
},
},
fr: {
translation: {
'error.generic': 'Une erreur est survenue. Veuillez réessayer plus tard.',
'error.network': 'Erreur réseau. Veuillez vérifier votre connexion Internet.',
},
},
},
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false, // non requis pour React car il échappe par défaut
},
});
function ErrorFallback({ error }) {
const { t } = useTranslation();
let errorMessageKey = 'error.generic';
if (error instanceof NetworkError) {
errorMessageKey = 'error.network';
}
return (
{t('error.generic')}
{t(errorMessageKey)}
);
}
function ErrorBoundary({ children }) {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
static getDerivedStateFromError = (error) => {
// Mettre à jour l'état pour que le prochain rendu affiche l'UI de secours
// return { hasError: true }; // ne fonctionne pas tel quel avec les hooks
setHasError(true);
setError(error);
}
if (hasError) {
// Vous pouvez afficher n'importe quelle UI de secours personnalisée
return ;
}
return children;
}
export default ErrorBoundary;
Dans cet exemple, nous utilisons i18next pour gérer les traductions pour l'anglais et le français. Le composant ErrorFallback utilise le hook useTranslation pour récupérer le message d'erreur approprié en fonction de la langue actuelle. Cela garantit que les utilisateurs voient les messages d'erreur dans leur langue préférée, améliorant ainsi l'expérience utilisateur globale.
Conclusion
Les composants ErrorBoundary de React sont un outil crucial pour construire des applications React robustes et conviviales. En implémentant des error boundaries, vous pouvez gérer les erreurs avec élégance, éviter les plantages d'application et offrir une meilleure expérience utilisateur aux utilisateurs du monde entier. En comprenant les principes des error boundaries, en mettant en œuvre des stratégies avancées comme les error boundaries granulaires, la journalisation des erreurs et les interfaces de secours personnalisées, et en évitant les pièges courants, vous pouvez construire des applications React plus résilientes et fiables qui répondent aux besoins d'un public mondial. N'oubliez pas de prendre en compte l'internationalisation et la localisation lors de l'affichage des messages d'erreur pour offrir une expérience utilisateur véritablement inclusive. À mesure que la complexité des applications web continue de croître, la maîtrise des techniques de gestion des erreurs deviendra de plus en plus importante pour les développeurs qui créent des logiciels de haute qualité.