Un guide complet pour comprendre et implémenter les Error Boundaries JavaScript dans React pour une gestion d'erreurs robuste et une dégradation élégante de l'UI.
Error Boundary JavaScript : Guide d'implémentation pour la gestion des erreurs React
Dans le monde du développement React, les erreurs inattendues peuvent entraîner des expériences utilisateur frustrantes et une instabilité de l'application. Une stratégie de gestion des erreurs bien définie est cruciale pour créer des applications robustes et fiables. Les Error Boundaries de React fournissent un mécanisme puissant pour gérer avec élégance les erreurs qui se produisent dans votre arborescence de composants, empêchant ainsi toute l'application de planter et vous permettant d'afficher une interface utilisateur de secours.
Qu'est-ce qu'un Error Boundary ?
Un Error Boundary est un composant React qui intercepte les erreurs JavaScript n'importe où dans son arborescence de composants enfants, consigne ces erreurs et affiche une interface utilisateur de secours à la place de l'arborescence de composants qui a planté. Les Error Boundaries interceptent les erreurs pendant le rendu, dans les méthodes de cycle de vie et dans les constructeurs de toute l'arborescence en dessous d'eux.
Pensez à un Error Boundary comme à un bloc try...catch
pour les composants React. Tout comme un bloc try...catch
vous permet de gérer les exceptions dans le code JavaScript synchrone, un Error Boundary vous permet de gérer les erreurs qui se produisent pendant le rendu de vos composants React.
Remarque importante : Les Error Boundaries n'interceptent pas les erreurs pour :
- Les gestionnaires d'événements (apprenez-en plus dans les sections suivantes)
- Le code asynchrone (par ex., les callbacks de
setTimeout
ourequestAnimationFrame
) - Le rendu côté serveur
- Les erreurs levées dans l'Error Boundary lui-même (plutôt que dans ses enfants)
Pourquoi utiliser les Error Boundaries ?
L'utilisation des Error Boundaries offre plusieurs avantages significatifs :
- Expérience utilisateur améliorée : Au lieu d'afficher un écran blanc ou un message d'erreur cryptique, vous pouvez montrer une interface utilisateur de secours conviviale, informant l'utilisateur que quelque chose s'est mal passé et offrant potentiellement un moyen de récupérer (par exemple, recharger la page ou naviguer vers une autre section).
- Stabilité de l'application : Les Error Boundaries empêchent les erreurs dans une partie de votre application de faire planter l'ensemble de l'application. Ceci est particulièrement important pour les applications complexes avec de nombreux composants interconnectés.
- Gestion centralisée des erreurs : Les Error Boundaries fournissent un emplacement centralisé pour consigner les erreurs et remonter à la cause première des problèmes. Cela simplifie le débogage et la maintenance.
- Dégradation élégante : Vous pouvez placer stratégiquement des Error Boundaries autour de différentes parties de votre application pour vous assurer que même si certains composants échouent, le reste de l'application reste fonctionnel. Cela permet une dégradation élégante face aux erreurs.
Implémenter les Error Boundaries dans React
Pour créer un Error Boundary, vous devez définir un composant de classe qui implémente l'une (ou les deux) des méthodes de cycle de vie suivantes :
static getDerivedStateFromError(error)
: Cette méthode de cycle de vie est appelé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 afin d'indiquer qu'une erreur s'est produite (par exemple, en passant un drapeauhasError
àtrue
).componentDidCatch(error, info)
: Cette méthode de cycle de vie est appelé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, ainsi qu'un objetinfo
contenant des informations sur le composant qui a levé l'erreur. Vous pouvez utiliser cette méthode pour consigner l'erreur dans un service comme Sentry ou Bugsnag.
Voici un exemple de base d'un composant Error Boundary :
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
// Met à jour l'état pour que le prochain rendu affiche l'UI de secours.
return {
hasError: true,
error: error
};
}
componentDidCatch(error, info) {
// Exemple de "componentStack":
// in ComponentThatThrows (created by App)
// in MyErrorBoundary (created by App)
// in div (created by App)
// in App
console.error("Caught an error:", error, info);
this.setState({
errorInfo: info.componentStack
});
// Vous pouvez également consigner l'erreur dans un service de rapport d'erreurs
//logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// Vous pouvez rendre n'importe quelle UI de secours personnalisée
return (
<div>
<h2>Quelque chose s'est mal passé.</h2>
<p>Erreur: {this.state.error ? this.state.error.message : "Une erreur inconnue est survenue."}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorInfo && this.state.errorInfo}
</details>
</div>
);
}
return this.props.children;
}
}
Pour utiliser l'Error Boundary, il suffit d'envelopper l'arborescence de composants que vous souhaitez protéger :
<ErrorBoundary>
<MyComponentThatMightThrow/>
</ErrorBoundary>
Exemples pratiques d'utilisation des Error Boundaries
Explorons quelques scénarios pratiques où les Error Boundaries peuvent être particulièrement utiles :
1. Gérer les erreurs d'API
Lors de la récupération de données depuis une API, des erreurs peuvent survenir en raison de problèmes de réseau, de problèmes de serveur ou de données invalides. Vous pouvez envelopper le composant qui récupère et affiche les données avec un Error Boundary pour gérer ces erreurs avec élégance.
function UserProfile() {
const [user, setUser] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(true);
React.useEffect(() => {
async function fetchData() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
// L'erreur sera interceptée par l'ErrorBoundary
throw error;
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
if (isLoading) {
return <p>Chargement du profil utilisateur...</p>;
}
if (!user) {
return <p>Aucune donnée utilisateur disponible.</p>;
}
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
function App() {
return (
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
);
}
Dans cet exemple, si l'appel API échoue ou renvoie une erreur, l'Error Boundary interceptera l'erreur et affichera une interface de secours (définie dans la méthode render
de l'Error Boundary). Cela empêche l'ensemble de l'application de planter et fournit à l'utilisateur un message plus informatif. Vous pourriez étendre l'interface de secours pour offrir une option de nouvelle tentative de la requête.
2. Gérer les erreurs des bibliothèques tierces
Lorsque vous utilisez des bibliothèques tierces, il est possible qu'elles lèvent des erreurs inattendues. Envelopper les composants qui utilisent ces bibliothèques avec des Error Boundaries peut vous aider à gérer ces erreurs avec élégance.
Considérez une bibliothèque de graphiques hypothétique qui lève occasionnellement des erreurs en raison d'incohérences de données ou d'autres problèmes. Vous pourriez envelopper le composant de graphique comme ceci :
function MyChartComponent() {
try {
// Rendre le graphique en utilisant la bibliothèque tierce
return <Chart data={data} />;
} catch (error) {
// Ce bloc catch ne sera pas efficace pour les erreurs de cycle de vie des composants React
// Il est principalement destiné aux erreurs synchrones au sein de cette fonction spécifique.
console.error("Error rendering chart:", error);
// Envisagez de lever l'erreur pour qu'elle soit interceptée par l'ErrorBoundary
throw error; // Relance de l'erreur
}
}
function App() {
return (
<ErrorBoundary>
<MyChartComponent />
</ErrorBoundary>
);
}
Si le composant Chart
lève une erreur, l'Error Boundary l'interceptera et affichera une interface de secours. Notez que le try/catch à l'intérieur de MyChartComponent n'interceptera que les erreurs au sein de la fonction synchrone, pas celles du cycle de vie du composant. Par conséquent, l'ErrorBoundary est essentiel ici.
3. Gérer les erreurs de rendu
Des erreurs peuvent survenir pendant le processus de rendu en raison de données invalides, de types de props incorrects ou d'autres problèmes. Les Error Boundaries peuvent intercepter ces erreurs et empêcher l'application de planter.
function DisplayName({ name }) {
if (typeof name !== 'string') {
throw new Error('Name must be a string');
}
return <h2>Bonjour, {name}!</h2>;
}
function App() {
return (
<ErrorBoundary>
<DisplayName name={123} /> <!-- Type de prop incorrect -->
</ErrorBoundary>
);
}
Dans cet exemple, le composant DisplayName
s'attend à ce que la prop name
soit une chaîne de caractères. Si un nombre est passé à la place, une erreur sera levée, et l'Error Boundary l'interceptera et affichera une interface de secours.
Error Boundaries et gestionnaires d'événements
Comme mentionné précédemment, les Error Boundaries n'interceptent pas les erreurs qui se produisent dans les gestionnaires d'événements. C'est parce que les gestionnaires d'événements sont généralement asynchrones, et les Error Boundaries n'interceptent que les erreurs qui se produisent pendant le rendu, dans les méthodes de cycle de vie et dans les constructeurs.
Pour gérer les erreurs dans les gestionnaires d'événements, vous devez utiliser un bloc try...catch
traditionnel au sein de la fonction du gestionnaire d'événements.
function MyComponent() {
const handleClick = () => {
try {
// Du code qui pourrait lever une erreur
throw new Error('Une erreur est survenue dans le gestionnaire d''événements');
} catch (error) {
console.error('Erreur interceptée dans le gestionnaire d''événements :', error);
// Gérer l'erreur (par ex., afficher un message d'erreur à l'utilisateur)
}
};
return <button onClick={handleClick}>Cliquez ici</button>;
}
Gestion globale des erreurs
Bien que les Error Boundaries soient excellents pour gérer les erreurs au sein de l'arborescence des composants React, ils ne couvrent pas tous les scénarios d'erreur possibles. Par exemple, ils n'interceptent pas les erreurs qui se produisent en dehors des composants React, comme les erreurs dans les écouteurs d'événements globaux ou les erreurs dans le code qui s'exécute avant l'initialisation de React.
Pour gérer ces types d'erreurs, vous pouvez utiliser le gestionnaire d'événements window.onerror
.
window.onerror = function(message, source, lineno, colno, error) {
console.error('Gestionnaire d''erreur global :', message, source, lineno, colno, error);
// Consigner l'erreur dans un service comme Sentry ou Bugsnag
// Afficher un message d'erreur global à l'utilisateur (optionnel)
return true; // Empêche le comportement de gestion d'erreur par défaut
};
Le gestionnaire d'événements window.onerror
est appelé chaque fois qu'une erreur JavaScript non interceptée se produit. Vous pouvez l'utiliser pour consigner l'erreur, afficher un message d'erreur global à l'utilisateur, ou prendre d'autres mesures pour gérer l'erreur.
Important : Retourner true
depuis le gestionnaire d'événements window.onerror
empêche le navigateur d'afficher le message d'erreur par défaut. Cependant, soyez attentif à l'expérience utilisateur ; si vous supprimez le message par défaut, assurez-vous de fournir une alternative claire et informative.
Meilleures pratiques pour l'utilisation des Error Boundaries
Voici quelques meilleures pratiques à garder à l'esprit lors de l'utilisation des Error Boundaries :
- Placez les Error Boundaries de manière stratégique : Enveloppez différentes parties de votre application avec des Error Boundaries pour isoler les erreurs et les empêcher de se propager en cascade. Envisagez d'envelopper des routes entières ou des sections majeures de votre interface utilisateur.
- Fournissez une interface de secours informative : L'interface de secours doit informer l'utilisateur qu'une erreur s'est produite et offrir potentiellement un moyen de récupérer. Évitez d'afficher des messages d'erreur génériques comme "Quelque chose s'est mal passé."
- Consignez les erreurs : Utilisez la méthode de cycle de vie
componentDidCatch
pour consigner les erreurs dans un service comme Sentry ou Bugsnag. Cela vous aidera à trouver la cause première des problèmes et à améliorer la stabilité de votre application. - N'utilisez pas les Error Boundaries pour les erreurs attendues : Les Error Boundaries sont conçus pour gérer les erreurs inattendues. Pour les erreurs attendues (par ex., erreurs de validation, erreurs d'API), utilisez des mécanismes de gestion d'erreurs plus spécifiques, comme des blocs
try...catch
ou des composants de gestion d'erreurs personnalisés. - Envisagez plusieurs niveaux d'Error Boundaries : Vous pouvez imbriquer des Error Boundaries pour fournir différents niveaux de gestion des erreurs. Par exemple, vous pourriez avoir un Error Boundary global qui intercepte toutes les erreurs non gérées et affiche un message d'erreur générique, et des Error Boundaries plus spécifiques qui interceptent les erreurs dans des composants particuliers et affichent des messages d'erreur plus détaillés.
- N'oubliez pas le rendu côté serveur : Si vous utilisez le rendu côté serveur, vous devrez également gérer les erreurs sur le serveur. Les Error Boundaries fonctionnent sur le serveur, mais vous pourriez avoir besoin d'utiliser des mécanismes de gestion d'erreurs supplémentaires pour intercepter les erreurs qui se produisent lors du rendu initial.
Techniques avancées d'Error Boundary
1. Utiliser une Render Prop
Au lieu de rendre une interface de secours statique, vous pouvez utiliser une render prop pour offrir plus de flexibilité dans la manière dont les erreurs sont gérées. Une render prop est une prop de type fonction qu'un composant utilise pour rendre quelque chose.
class ErrorBoundary extends React.Component {
// ... (identique à avant)
render() {
if (this.state.hasError) {
// Utiliser la render prop pour rendre l'UI de secours
return this.props.fallbackRender(this.state.error, this.state.errorInfo);
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary fallbackRender={(error, errorInfo) => (
<div>
<h2>Quelque chose s'est mal passé !</h2>
<p>Erreur: {error.message}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{errorInfo.componentStack}
</details>
</div>
)}>
<MyComponentThatMightThrow/>
</ErrorBoundary>
);
}
Cela vous permet de personnaliser l'interface de secours pour chaque Error Boundary. La prop fallbackRender
reçoit l'erreur et les informations sur l'erreur en arguments, vous permettant d'afficher des messages d'erreur plus spécifiques ou de prendre d'autres mesures en fonction de l'erreur.
2. L'Error Boundary en tant que Composant d'Ordre Supérieur (HOC)
Vous pouvez créer un composant d'ordre supérieur (HOC) qui enveloppe un autre composant avec un Error Boundary. Cela peut être utile pour appliquer des Error Boundaries à plusieurs composants sans avoir à répéter le même code.
function withErrorBoundary(WrappedComponent) {
return class WithErrorBoundary extends React.Component {
render() {
return (
<ErrorBoundary>
<WrappedComponent {...this.props} />
</ErrorBoundary>
);
}
};
}
// Utilisation :
const MyComponentWithErrorHandling = withErrorBoundary(MyComponentThatMightThrow);
La fonction withErrorBoundary
prend un composant en argument et retourne un nouveau composant qui enveloppe le composant original avec un Error Boundary. Cela vous permet d'ajouter facilement la gestion des erreurs à n'importe quel composant de votre application.
Tester les Error Boundaries
Il est important de tester vos Error Boundaries pour vous assurer qu'ils fonctionnent correctement. Vous pouvez utiliser des bibliothèques de test comme Jest et React Testing Library pour tester vos Error Boundaries.
Voici un exemple de comment tester un Error Boundary en utilisant React Testing Library :
import { render, screen, fireEvent } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
function ComponentThatThrows() {
throw new Error('This component throws an error');
}
test('renders fallback UI when an error is thrown', () => {
render(
<ErrorBoundary>
<ComponentThatThrows />
</ErrorBoundary>
);
expect(screen.getByText('Quelque chose s''est mal passé.')).toBeInTheDocument();
});
Ce test effectue le rendu du composant ComponentThatThrows
, qui lève une erreur. Le test vérifie ensuite que l'interface de secours rendue par l'Error Boundary est bien affichée.
Error Boundaries et Server Components (React 18+)
Avec l'introduction des Server Components dans React 18 et les versions ultérieures, les Error Boundaries continuent de jouer un rôle vital dans la gestion des erreurs. Les Server Components s'exécutent sur le serveur et n'envoient que le résultat rendu au client. Bien que les principes de base restent les mêmes, il y a quelques nuances à prendre en compte :
- Journalisation des erreurs côté serveur : Assurez-vous de consigner les erreurs qui se produisent dans les Server Components sur le serveur. Cela peut impliquer l'utilisation d'un framework de journalisation côté serveur ou l'envoi des erreurs à un service de suivi des erreurs.
- Interface de secours côté client : Même si les Server Components sont rendus sur le serveur, vous devez toujours fournir une interface de secours côté client en cas d'erreur. Cela garantit que l'utilisateur a une expérience cohérente, même si le serveur ne parvient pas à rendre le composant.
- Streaming SSR : Lorsque vous utilisez le rendu côté serveur en streaming (SSR), des erreurs peuvent survenir pendant le processus de streaming. Les Error Boundaries peuvent vous aider à gérer ces erreurs avec élégance en rendant une interface de secours pour le flux affecté.
La gestion des erreurs dans les Server Components est un domaine en évolution, il est donc important de se tenir au courant des dernières meilleures pratiques et recommandations.
Pièges courants à éviter
- Dépendance excessive aux Error Boundaries : N'utilisez pas les Error Boundaries comme substitut à une gestion appropriée des erreurs dans vos composants. Efforcez-vous toujours d'écrire un code robuste et fiable qui gère les erreurs avec élégance.
- Ignorer les erreurs : Assurez-vous de consigner les erreurs interceptées par les Error Boundaries afin de pouvoir remonter à la cause première des problèmes. Ne vous contentez pas d'afficher une interface de secours et d'ignorer l'erreur.
- Utiliser les Error Boundaries pour les erreurs de validation : Les Error Boundaries ne sont pas l'outil approprié pour gérer les erreurs de validation. Utilisez plutôt des techniques de validation plus spécifiques.
- Ne pas tester les Error Boundaries : Testez vos Error Boundaries pour vous assurer qu'ils fonctionnent correctement.
Conclusion
Les Error Boundaries sont un outil puissant pour créer des applications React robustes et fiables. En comprenant comment implémenter et utiliser efficacement les Error Boundaries, vous pouvez améliorer l'expérience utilisateur, prévenir les plantages d'applications et simplifier le débogage. N'oubliez pas de placer les Error Boundaries de manière stratégique, de fournir une interface de secours informative, de consigner les erreurs et de tester vos Error Boundaries de manière approfondie.
En suivant les directives et les meilleures pratiques décrites dans ce guide, vous pouvez vous assurer que vos applications React sont résilientes aux erreurs et offrent une expérience positive à vos utilisateurs.