Apprenez à catégoriser et à gérer efficacement les erreurs dans les Error Boundaries de React, améliorant ainsi la stabilité de l'application et l'expérience utilisateur.
Catégorisation des erreurs dans les Error Boundaries de React : Un guide complet
La gestion des erreurs est un aspect essentiel de la création d'applications React robustes et maintenables. Bien que les Error Boundaries de React fournissent un mécanisme pour gérer avec élégance les erreurs qui se produisent pendant le rendu, comprendre comment catégoriser et répondre aux différents types d'erreurs est crucial pour créer une application véritablement résiliente. Ce guide explore diverses approches de la catégorisation des erreurs au sein des Error Boundaries, en offrant des exemples pratiques et des perspectives concrètes pour améliorer votre stratégie de gestion des erreurs.
Que sont les Error Boundaries de React ?
Introduits dans React 16, les Error Boundaries sont des composants React qui interceptent les erreurs JavaScript n'importe où dans leur arbre de composants enfants, journalisent ces erreurs et affichent une interface utilisateur de secours au lieu de faire planter tout l'arbre de composants. Ils fonctionnent de manière similaire à un bloc try...catch, mais pour les composants.
Caractéristiques clés des Error Boundaries :
- Gestion des erreurs au niveau du composant : Isolez les erreurs dans des sous-arbres de composants spécifiques.
- Dégradation gracieuse : Empêchez l'application entière de planter à cause d'une seule erreur de composant.
- Interface utilisateur de secours contrôlée : Affichez un message convivial ou un contenu alternatif lorsqu'une erreur se produit.
- Journalisation des erreurs : Facilitez le suivi et le débogage des erreurs en enregistrant les informations sur l'erreur.
Pourquoi catégoriser les erreurs dans les Error Boundaries ?
Le simple fait d'intercepter les erreurs ne suffit pas. Une gestion efficace des erreurs nécessite de comprendre ce qui n'a pas fonctionné et de réagir en conséquence. La catégorisation des erreurs au sein des Error Boundaries offre plusieurs avantages :
- Gestion ciblée des erreurs : Différents types d'erreurs peuvent nécessiter des réponses différentes. Par exemple, une erreur réseau pourrait justifier un mécanisme de nouvelle tentative, tandis qu'une erreur de validation de données pourrait nécessiter une correction de la saisie par l'utilisateur.
- Expérience utilisateur améliorée : Affichez des messages d'erreur plus informatifs en fonction du type d'erreur. Un message générique "Quelque chose s'est mal passé" est moins utile qu'un message spécifique indiquant un problème de réseau ou une saisie invalide.
- Débogage amélioré : La catégorisation des erreurs fournit un contexte précieux pour le débogage et l'identification de la cause première des problèmes.
- Surveillance proactive : Suivez la fréquence des différents types d'erreurs pour identifier les problèmes récurrents et prioriser les correctifs.
- Interface utilisateur de secours stratégique : Affichez différentes interfaces de secours en fonction de l'erreur, fournissant des informations ou des actions plus pertinentes à l'utilisateur.
Approches de la catégorisation des erreurs
Plusieurs techniques peuvent être employées pour catégoriser les erreurs au sein des Error Boundaries de React :
1. Utiliser instanceof
L'opérateur instanceof vérifie si un objet est une instance d'une classe particulière. C'est utile pour catégoriser les erreurs en fonction de leurs types d'erreurs intégrés ou personnalisés.
Exemple :
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = "NetworkError";
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class MyErrorBoundary 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, error: error };
}
componentDidCatch(error, errorInfo) {
// Vous pouvez également journaliser l'erreur dans un service de rapport d'erreurs
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// Vous pouvez afficher n'importe quelle UI de secours personnalisée
let errorMessage = "Quelque chose s'est mal passé.";
if (this.state.error instanceof NetworkError) {
errorMessage = "Une erreur réseau s'est produite. Veuillez vérifier votre connexion et réessayer.";
} else if (this.state.error instanceof ValidationError) {
errorMessage = "Il y a eu une erreur de validation. Veuillez vérifier votre saisie.";
}
return (
<div>
<h2>Erreur !</h2>
<p>{errorMessage}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
Explication :
- Des classes personnalisées
NetworkErroretValidationErrorsont définies, étendant la classeErrorintégrée. - Dans la méthode
renderdu composantMyErrorBoundary, l'opérateurinstanceofest utilisé pour vérifier le type de l'erreur interceptée. - En fonction du type d'erreur, un message d'erreur spécifique est affiché dans l'interface de secours.
2. Utiliser des codes ou des propriétés d'erreur
Une autre approche consiste à inclure des codes ou des propriétés d'erreur dans l'objet d'erreur lui-même. Cela permet une catégorisation plus fine basée sur des scénarios d'erreur spécifiques.
Exemple :
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) {
const error = new Error("La requête réseau a échoué");
error.code = response.status; // Ajouter un code d'erreur personnalisé
reject(error);
}
return response.json();
})
.then(data => resolve(data))
.catch(error => reject(error));
});
}
class MyErrorBoundary 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, error: error };
}
componentDidCatch(error, errorInfo) {
// Vous pouvez également journaliser l'erreur dans un service de rapport d'erreurs
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
let errorMessage = "Quelque chose s'est mal passé.";
if (this.state.error.code === 404) {
errorMessage = "Ressource non trouvée.";
} else if (this.state.error.code >= 500) {
errorMessage = "Erreur serveur. Veuillez réessayer plus tard.";
}
return (
<div>
<h2>Erreur !</h2>
<p>{errorMessage}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
Explication :
- La fonction
fetchDataajoute une propriétécodeà l'objet d'erreur, représentant le code de statut HTTP. - Le composant
MyErrorBoundaryvérifie la propriétécodepour déterminer le scénario d'erreur spécifique. - Différents messages d'erreur sont affichés en fonction du code d'erreur.
3. Utiliser un mappage d'erreurs centralisé
Pour les applications complexes, maintenir un mappage d'erreurs centralisé peut améliorer l'organisation et la maintenabilité du code. Cela implique de créer un dictionnaire ou un objet qui mappe les types ou codes d'erreur à des messages d'erreur et à une logique de gestion spécifiques.
Exemple :
const errorMap = {
"NETWORK_ERROR": {
message: "Une erreur réseau s'est produite. Veuillez vérifier votre connexion.",
retry: true,
},
"INVALID_INPUT": {
message: "Saisie invalide. Veuillez vérifier vos données.",
retry: false,
},
404: {
message: "Ressource non trouvée.",
retry: false,
},
500: {
message: "Erreur serveur. Veuillez réessayer plus tard.",
retry: true,
},
"DEFAULT": {
message: "Quelque chose s'est mal passé.",
retry: false,
},
};
function handleCustomError(errorType) {
const errorDetails = errorMap[errorType] || errorMap["DEFAULT"];
return errorDetails;
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorDetails: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Mettre à jour l'état pour que le prochain rendu affiche l'UI de secours.
const errorDetails = handleCustomError(error.message);
return { hasError: true, errorDetails: errorDetails };
}
componentDidCatch(error, errorInfo) {
// Vous pouvez également journaliser l'erreur dans un service de rapport d'erreurs
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
const { message } = this.state.errorDetails;
return (
<div>
<h2>Erreur !</h2>
<p>{message}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorDetails.message}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
function MyComponent(){
const [data, setData] = React.useState(null);
React.useEffect(() => {
try {
throw new Error("NETWORK_ERROR");
} catch (e) {
throw e;
}
}, []);
return <div></div>;
}
Explication :
- L'objet
errorMapstocke les informations sur les erreurs, y compris les messages et les indicateurs de nouvelle tentative, en fonction des types ou codes d'erreur. - La fonction
handleCustomErrorrécupère les détails de l'erreur depuiserrorMapen fonction du message d'erreur et renvoie des valeurs par défaut si aucun code spécifique n'est trouvé. - Le composant
MyErrorBoundaryutilisehandleCustomErrorpour obtenir le message d'erreur approprié depuiserrorMap.
Meilleures pratiques pour la catégorisation des erreurs
- Définir des types d'erreurs clairs : Établissez un ensemble cohérent de types ou de codes d'erreur pour votre application.
- Fournir des informations contextuelles : Incluez des détails pertinents dans les objets d'erreur pour faciliter le débogage.
- Centraliser la logique de gestion des erreurs : Utilisez un mappage d'erreurs centralisé ou des fonctions utilitaires pour gérer les erreurs de manière cohérente.
- Journaliser efficacement les erreurs : Intégrez des services de rapport d'erreurs pour suivre et analyser les erreurs en production. Les services populaires incluent Sentry, Rollbar et Bugsnag.
- Tester la gestion des erreurs : Rédigez des tests unitaires pour vérifier que vos Error Boundaries gèrent correctement les différents types d'erreurs.
- Tenir compte de l'expérience utilisateur : Affichez des messages d'erreur informatifs et conviviaux qui guident les utilisateurs vers une résolution. Évitez le jargon technique.
- Surveiller les taux d'erreur : Suivez la fréquence des différents types d'erreurs pour identifier les problèmes récurrents et prioriser les correctifs.
- Internationalisation (i18n) : Lorsque vous présentez des messages d'erreur à l'utilisateur, assurez-vous que vos messages sont correctement internationalisés pour prendre en charge différentes langues et cultures. Utilisez des bibliothèques comme
i18nextou l'API Context de React pour gérer les traductions. - Accessibilité (a11y) : Assurez-vous que vos messages d'erreur sont accessibles aux utilisateurs handicapés. Utilisez des attributs ARIA pour fournir un contexte supplémentaire aux lecteurs d'écran.
- Sécurité : Soyez prudent quant aux informations que vous affichez dans les messages d'erreur, en particulier dans les environnements de production. Évitez d'exposer des données sensibles qui pourraient être exploitées par des attaquants. Par exemple, n'affichez pas les traces de pile brutes aux utilisateurs finaux.
Scénario d'exemple : Gérer les erreurs d'API dans une application de commerce électronique
Considérez une application de commerce électronique qui récupère les informations sur les produits à partir d'une API. Les scénarios d'erreur potentiels incluent :
- Erreurs réseau : Le serveur API est indisponible ou la connexion Internet de l'utilisateur est interrompue.
- Erreurs d'authentification : Le jeton d'authentification de l'utilisateur est invalide ou a expiré.
- Erreurs de ressource non trouvée : Le produit demandé n'existe pas.
- Erreurs serveur : Le serveur API rencontre une erreur interne.
En utilisant les Error Boundaries et la catégorisation des erreurs, l'application peut gérer ces scénarios avec élégance :
// Exemple (simplifié)
async function fetchProduct(productId) {
try {
const response = await fetch(`/api/products/${productId}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error("PRODUCT_NOT_FOUND");
} else if (response.status === 401 || response.status === 403) {
throw new Error("AUTHENTICATION_ERROR");
} else {
throw new Error("SERVER_ERROR");
}
}
return await response.json();
} catch (error) {
if (error instanceof TypeError && error.message === "Failed to fetch") {
throw new Error("NETWORK_ERROR");
}
throw error;
}
}
class ProductErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorDetails: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
const errorDetails = handleCustomError(error.message); // Utiliser errorMap comme montré précédemment
return { hasError: true, errorDetails: errorDetails };
}
componentDidCatch(error, errorInfo) {
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
const { message, retry } = this.state.errorDetails;
return (
<div>
<h2>Erreur !</h2>
<p>{message}</p>
{retry && <button onClick={() => window.location.reload()}>Réessayer</button>}
</div>
);
}
return this.props.children;
}
}
Explication :
- La fonction
fetchProductvérifie le code de statut de la réponse de l'API et lève des types d'erreurs spécifiques en fonction du statut. - Le composant
ProductErrorBoundaryintercepte ces erreurs et affiche des messages d'erreur appropriés. - Pour les erreurs réseau et les erreurs serveur, un bouton "Réessayer" est affiché, permettant à l'utilisateur de tenter à nouveau la requête.
- Pour les erreurs d'authentification, l'utilisateur pourrait être redirigé vers la page de connexion.
- Pour les erreurs de ressource non trouvée, un message indiquant que le produit n'existe pas est affiché.
Conclusion
La catégorisation des erreurs au sein des Error Boundaries de React est essentielle pour créer des applications résilientes et conviviales. En employant des techniques comme les vérifications avec instanceof, les codes d'erreur et les mappages d'erreurs centralisés, vous pouvez gérer efficacement différents scénarios d'erreur et offrir une meilleure expérience utilisateur. N'oubliez pas de suivre les meilleures pratiques pour la gestion des erreurs, la journalisation et les tests afin de vous assurer que votre application gère avec élégance les situations inattendues.
En mettant en œuvre ces stratégies, vous pouvez améliorer considérablement la stabilité et la maintenabilité de vos applications React, offrant une expérience plus fluide et plus fiable à vos utilisateurs, quel que soit leur emplacement ou leur contexte.
Ressources supplémentaires :