
Gestion des erreurs d'API
Apprenez les meilleures pratiques pour gérer les erreurs d'API (réseau, HTTP 4xx/5xx, erreurs métier) en React, améliorer l'UX et faciliter le débogage.
Anticiper l'inattendu : Pourquoi la gestion d'erreurs est essentielle
Lors de la communication avec des API externes, il est illusoire de supposer que tout se passera toujours bien. Les réseaux peuvent être instables, les serveurs peuvent tomber en panne, les requêtes peuvent être mal formées, les autorisations peuvent être refusées, ou le backend peut simplement retourner une erreur métier spécifique. Ignorer ces possibilités conduit inévitablement à une mauvaise expérience utilisateur (UX) et rend le débogage extrêmement difficile.
Une gestion d'erreurs d'API robuste est cruciale pour :
- Informer l'utilisateur : Afficher des messages clairs et utiles lorsque quelque chose ne fonctionne pas, plutôt que de laisser l'interface dans un état incohérent ou vide.
- Permettre des actions correctives : Guider l'utilisateur sur les prochaines étapes possibles (réessayer, contacter le support, vérifier sa connexion).
- Prévenir les plantages : Eviter que des erreurs non gérées ne fassent planter l'application JavaScript.
- Faciliter le débogage : Enregistrer (logger) les erreurs détaillées pour aider les développeurs à identifier et corriger les problèmes rapidement.
- Améliorer la résilience : Mettre en place des stratégies de nouvelle tentative (retry) pour les erreurs temporaires.
Une bonne stratégie de gestion des erreurs prend en compte les différents types d'erreurs qui peuvent survenir lors d'un appel API.
Comprendre les différents types d'erreurs API
Lorsqu'une requête API échoue, l'erreur peut provenir de différentes sources :
- Erreurs Réseau : Problèmes de connectivité empêchant la requête d'atteindre le serveur (ex: pas d'internet, DNS introuvable, timeout). L'API `fetch` rejette sa promesse dans ce cas. `axios` fournit des informations via `error.request`.
- Erreurs HTTP (Statuts 4xx et 5xx) : Le serveur a reçu la requête mais ne peut pas la traiter correctement.
- Erreurs Client (4xx) : Indiquent un problème avec la requête envoyée par le client (ex: `400 Bad Request` - données invalides, `401 Unauthorized` - authentification requise/échouée, `403 Forbidden` - droits insuffisants, `404 Not Found` - ressource inexistante).
- Erreurs Serveur (5xx) : Indiquent un problème côté serveur (ex: `500 Internal Server Error`, `502 Bad Gateway`, `503 Service Unavailable`).
- Erreurs Applicatives / Métier : Le serveur répond avec un statut HTTP de succès (ex: `200 OK`), mais le corps de la réponse contient une structure indiquant une erreur spécifique à la logique métier (ex: `{ success: false, error: { code: 'INSUFFICIENT_FUNDS', message: 'Solde insuffisant' } }`). Celles-ci doivent être détectées en analysant le corps de la réponse.
Votre gestion d'erreurs doit idéalement pouvoir distinguer ces différents cas pour réagir de manière appropriée.
Stratégies de gestion dans `useEffect` (fetch / axios)
Comme vu précédemment, le bloc `try...catch` est essentiel pour intercepter les erreurs réseau ou les erreurs levées manuellement.
Avec `fetch` :
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
setLoading(true); setError(null);
try {
const response = await fetch(url, { signal: controller.signal });
// 1. Gestion des erreurs HTTP
if (!response.ok) {
let errorInfo = { status: response.status, message: response.statusText };
try {
// Essayer de lire un message d'erreur du backend
const body = await response.json();
errorInfo.backendMessage = body.message || body.error;
} catch (e) { /* Ignorer si le corps n'est pas JSON */ }
throw new Error(JSON.stringify(errorInfo)); // Levez une erreur avec les détails
}
const data = await response.json();
// 2. Gestion des erreurs applicatives (si applicable)
if (data.success === false || data.error) {
throw new Error(data.message || data.error || 'Erreur applicative inconnue');
}
setData(data.results); // Succès
} catch (err) {
if (err.name !== 'AbortError') {
// 3. Mise à jour de l'état d'erreur
setError(err.message); // Stocker un message simple ou l'objet JSON.parse(err.message)
console.error("Erreur Fetch:", err);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, [/* dépendances */]);Avec `axios` : La gestion est souvent plus simple car les erreurs HTTP rejettent la promesse.
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
setLoading(true); setError(null);
try {
const response = await axios.get(url, { signal: controller.signal });
// Gestion optionnelle des erreurs applicatives dans la réponse
if (response.data.success === false) {
throw new Error(response.data.message || 'Erreur applicative');
}
setData(response.data.results); // Succès
} catch (err) {
if (axios.isCancel(err)) { // Ignorer l'annulation
console.log('Requête Axios annulée');
} else if (err.response) {
// Erreur HTTP (4xx, 5xx)
setError(`Erreur ${err.response.status}: ${err.response.data?.message || err.message}`);
console.error('Erreur Axios Response:', err.response);
} else if (err.request) {
// Erreur Réseau (pas de réponse)
setError('Erreur réseau ou serveur inaccessible.');
console.error('Erreur Axios Request:', err.request);
} else {
// Autre erreur (configuration, erreur applicative levée...)
setError(err.message);
console.error('Erreur Axios Générale:', err);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, [/* dépendances */]);Fournir un retour utilisateur et journaliser (logging)
Une fois l'erreur capturée et stockée dans l'état `error`, il faut l'utiliser pour informer l'utilisateur. Le message affiché doit être clair et compréhensible, sans exposer de détails techniques sensibles.
// Dans le rendu du composant
if (error) {
return (
Oops ! Une erreur est survenue.
{getUserFriendlyErrorMessage(error)}
{/* Optionnel : Bouton pour réessayer */}
);
}
La fonction `getUserFriendlyErrorMessage` pourrait analyser le message d'erreur stocké pour afficher quelque chose de pertinent (ex: "Identifiants incorrects", "Vous n'avez pas les droits", "Problème de connexion", "Une erreur serveur s'est produite, veuillez réessayer plus tard.").
Parallèlement, il est crucial de journaliser (logger) les détails techniques de l'erreur (l'objet d'erreur complet, le statut, le corps de la réponse d'erreur) pour les développeurs. Cela peut se faire via `console.error` pendant le développement, mais en production, il est préférable d'envoyer ces logs à un service de suivi d'erreurs dédié (comme Sentry, LogRocket, Datadog) pour pouvoir les analyser et les corriger proactivement.
Simplification avec les bibliothèques de Data Fetching
Les bibliothèques comme React Query (TanStack Query) et SWR simplifient considérablement la gestion des erreurs :
- Elles fournissent directement un état `error` dans l'objet retourné par leurs hooks (`useQuery`, `useSWR`).
- Elles gèrent automatiquement les retries pour les erreurs temporaires (configurable).
- Elles offrent des callbacks (`onError` dans `useQuery` ou `useMutation`, option `onError` dans `useSWR`) pour exécuter une logique spécifique en cas d'erreur (ex: afficher une notification globale, logger l'erreur).
Cela réduit le code manuel nécessaire dans le `catch` et centralise une partie de la logique d'erreur via les options des hooks.
// Avec React Query
const { data, error, isLoading } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
retry: 1, // Réessayer 1 fois en cas d'erreur
onError: (err) => {
console.error("Erreur useQuery:", err);
// Envoyer à un service de logging
logErrorToService(err);
// Afficher une notification globale si besoin
showGlobalErrorToast('Erreur lors du chargement des tâches.');
}
});
if (isLoading) return /* ... */ ;
// L'état 'error' contient l'objet d'erreur si la dernière tentative a échoué
if (error) return Erreur: {error.message};
En conclusion, une gestion proactive et structurée des erreurs API est indispensable. Il faut identifier les différents types d'erreurs, les intercepter (avec `try...catch`, vérification de statut, analyse de réponse), mettre à jour un état d'erreur dédié, fournir un retour clair à l'utilisateur, et journaliser les détails techniques. Les bibliothèques de data fetching peuvent grandement faciliter ce processus.