Comment bien gérer les erreurs avec Remix ? (ErrorBoundary)

Comment bien gérer les erreurs avec Remix ? (ErrorBoundary)

As-tu déjà eu des erreurs Javascript en production ? Pire encore, as-tu reçu l’appel d’un client, qui se plaint d’avoir reçu un message d’erreur ?
Tu perds de la crédibilité.

Heureusement, il existe le composant ErrorBoundary qui permet d’afficher un composant d’erreur, respectant le thème du site et ton Design System.

Avant d’utiliser ErrorBoundary

Tu retrouves un message d’erreur générique incompréhensible. Difficile de comprendre ce qui a pu se passer, et tu vas prendre du temps à réparer le bug. De plus, ton site est inutilisable en l’état. La navigation de ton utilisateur a été coupée brutalement.

Après avoir mis en place ErrorBoundary

Tu améliores l’expérience utilisateur. Tu réduis l’impact de l’erreur à l’endroit où elle a été déclenchée. Cela permet au développeur de vite résoudre le problème, car il sait exactement de quel fichier il s’agit.

Dans Remix, on utilise le composant ErrorBoundary, qui va remplacer le composant ayant déclenché l’erreur.

Tu retrouves cet exemple et davantage d’information dans la documentation de Remix concernant l’error handling

Pour améliorer l’expérience utilisateur (UX), il est recommandé d’offrir une explication claire dans le message d’erreur, comme expliqué dans la figure ci-dessous (Source).

Un message d’erreur idéal :

rassure le client

explique la raison du problème

offre une marche à suivre pour arranger les choses (contacter le support)

Qu’est-ce que le composant ErrorBoundary ?

C’est une librairie Javascript, disponible sur NPM sous le nom de react-error-boundary. Elle est téléchargée plus de 3,7 millions de fois par semaine.

Pour l’utiliser dans une application React standard, il suffit de la télécharger dans ton projet :

npm install react-error-boundary

Ensuite, tu l’utilises dans un composant React. Elle va englober le composant qui risque de déclencher une erreur, et afficher un composant fallback à la place.

import { ErrorBoundary } from react-error-boundary;

<ErrorBoundary fallback={<div>Something went wrong</div>}>
<ExampleApplication />
</ErrorBoundary>;

Cet exemple montre deux composants :

Le composant parent ErrorBoundary, qui prend un children (le composant enfant) et une propriété fallback (affichant le composant d’erreur)
Le composant enfant ExampleApplication. En cas d’erreur de ce dernier, l’erreur ne va pas remonter à la racine de l’application. À la place, l’erreur va afficher le composant fallback.

Il y a une deuxième manière d’utiliser ce composant, offrant plus de contrôle et une meilleure UX.

import { ErrorBoundary } from react-error-boundary;

function fallbackRender({ error, resetErrorBoundary }) {
// Call resetErrorBoundary() to reset the error boundary and retry the render.

return (
<div role=‘alert’>
<p>Something went wrong:</p>
<pre style={{ color: red }}>{error.message}</pre>
</div>
);
}

<ErrorBoundary
fallbackRender={fallbackRender}
onReset={(details) => {
// Reset the state of your app so the error doesn’t happen again
}}
>
<ExampleApplication />
</ErrorBoundary>;

La propriété fallbackRender accepte une fonction fléchée, nous permettant de récupérer deux arguments :

Les informations sur l’erreur qui a été déclenchée
Une fonction permettant de ré-effectuer un rendu du composant. Déclencher cette fonction permet d’annuler l’erreur, et d’afficher le composant enfant.

Généralement, on laisse l’utilisateur appeler cette méthode resetErrorBoundary. Plus haut, par exemple en utilisant le bouton Try Again.

Mais cet article ne se concentre pas sur ce composant très utile.

Si tu utilises Remix, tu n’as même pas besoin de l’utiliser.

Il est intégré directement dans la librairie !

Comment gérer tes erreurs avec Remix

Pour déclencher une erreur dans ton application Remix, il te suffit d’ajouter l’instruction throw new Error(‘bam’) sur n’importe quelle route active.

Ce qui affiche ceci :

Cette erreur n’est pas claire du tout. Et elle rend ton application inutilisable pour les clients : Ils ne peuvent pas accéder à une autre page depuis l’interface.

De plus, cela ne t’aide pas à connaître la raison de l’erreur : elle pourrait être déclenchée par n’importe quoi.

Comment faire ? Il suffit d’ exporter un composant nommé ErrorBoundary dans ton application Remix. Commence par l’importer dans le fichier root.tsx, se situant dans le dossier app/routes de ton projet Remix.

Le composant ErrorBoundary (Remix)

Le composant ErrorBoundary est une convention de Remix (au même titre que les méthodes loader et action). Exporter un composant nommé “ErrorBoundary” dans n’importe quelle route indique à Remix qu’en cas d’erreur, il doit afficher son contenu, et ne pas propager l’erreur plus haut.

Prenons par exemple l’export ci-dessous :

export function ErrorBoundary() {
const error = useRouteError();
let errorMessage = Une erreur inattendue est survenue;

if (error instanceof Error) {
errorMessage = error.message;
}

return <div>{errorMessage}</span>;
}

Après avoir ajouté ces quelques lignes, notre application ressemble à ça :

On avait rajouté la ligne throw new Error(‘bam’);. Au lieu d’afficher l’erreur en rouge, sans design, notre composant ErrorBoundary est affiché.

Cela nous apporte quelques bénéfices :

L’erreur ne se propage pas au dessus (on y revient un peu plus bas)
On peut designer notre message d’erreur, expliquer au client ce qui s’est passé, et lui indiquer la marche à suivre
En bonus, on peut sauvegarder cette erreur avec un outil comme Sentry, ou l’envoyer par email pour être prévenu si jamais elle est déclenchée en production.

Les erreurs ne remontent pas

React utilise un système de composants, avec des balises imbriquées. Ce qui donne une hiérarchie avec des “parents” et des “enfants”.

Avec Remix, il est possible d’implémenter des routes imbriquées. Cela signifie qu’il est possible d’avoir plusieurs routes actives en même temps.

C’est ce que nous montre l’image, que nous avions vu au dessus :

Cette image représente une route imbriquée. Quatres routes sont actuellement actives :

La route root principale. C’est notre composant d’entrée, cette route est rendue sur toutes les routes. La route root affiche la barre latérale, sur le côté gauche.
La route sales (on la voit comme premier segment dans l’URL) affiche le titre de la page “Sales”, ainsi qu’une barre de navigation horizontale.
La route invoices (on retrouve son segment dans l’URL) est “enfant” de la route Sales, et affiche les statistiques globales, la barre jaune et verte ainsi que le tableau de factures
La route dynamique 102000 (où 102000 représente une id de facture) est imbriquée dans invoices (encore une fois, on le constate grâce au segment d’URL final). C’est le dernier enfant de cette hiérarchie, l’enfant le plus bas. Et il est en erreur.

On peut symboliser cette vue d’une autre manière :

export default function Page () {
return (<Sales>
<Invoices>
<Invoice id=“102000”/>
</Invoices
</Sales>)
}

Chaque imbrication représente un enfant dans notre composant. Et le composant ErrorBoundary empêche notre erreur de remonter plus haut.

Ce qui est représenté par l’image ci-dessus, au final, est un message d’erreur qui a empêché une erreur déclenchée dans le composant Invoice de remonter plus haut.

C’est un peu comme s’il s’était passé la chose suivante :

export default function Page() {
return (
<Sales>
<Invoices>
<ErrorBoundary fallback={<Error message=‘Something went wrong’ />}>
<Invoice id=‘102000’ />
</ErrorBoundary>
</Invoices>
</Sales>
);
}

Remix a remplacé le composant Invoice par un composant Error, car ce fichier était protégé par une fonction ErrorBoundary.

En ajoutant ce composant sur notre route imbriquée, nous améliorons considérablement l’expérience de nos utilisateurs. De plus, nos développeurs identifient plus rapidement le problème, car le composant en erreur est au plus bas dans la hiérarchie.