Maîtrisez la gestion des erreurs React useActionState. Découvrez une stratégie complète pour la récupération d'erreur, la préservation des entrées utilisateur et la création de formulaires résilients pour un public mondial.
Récupération d'erreur React useActionState : Une stratégie complète de gestion des erreurs d'action
Dans le monde du développement web, l'expérience utilisateur d'un formulaire est un point de contact essentiel. Un formulaire transparent et intuitif peut mener à une conversion réussie, tandis qu'un formulaire frustrant peut amener les utilisateurs à abandonner complètement une tâche. Avec l'introduction des Actions Serveur et du nouveau hook useActionState dans React 19, les développeurs disposent d'outils puissants pour gérer les soumissions de formulaires et les transitions d'état. Cependant, il ne suffit plus d'afficher un simple message d'erreur lorsqu'une action échoue.
Une application véritablement robuste anticipe les échecs et fournit à l'utilisateur un chemin clair vers la récupération. Que se passe-t-il lorsqu'une connexion réseau est interrompue ? Ou lorsque la saisie d'un utilisateur échoue à la validation côté serveur ? L'utilisateur perd-il toutes les données qu'il vient de passer des minutes à saisir ? C'est là qu'une stratégie sophistiquée de gestion des erreurs et de récupération devient essentielle.
Ce guide complet vous emmènera au-delà des bases de useActionState. Nous explorerons une stratégie complète pour gérer les erreurs d'action, préserver les entrées utilisateur et créer des formulaires résilients et conviviaux qui fonctionnent de manière fiable pour un public mondial. Nous passerons de la théorie à la mise en œuvre pratique, en construisant un système à la fois puissant et maintenable.
Qu'est-ce que `useActionState` ? Un bref rappel
Avant de plonger dans notre stratégie de récupération, revenons brièvement sur le hook useActionState (qui était connu sous le nom de useFormState dans les premières versions expérimentales de React). Son objectif principal est de gérer l'état d'une action de formulaire, y compris les états en attente et les données renvoyées par le serveur.
Il simplifie un modèle qui nécessitait auparavant une combinaison de useState, useEffect et une gestion manuelle de l'état pour gérer les soumissions de formulaires.
La syntaxe de base est la suivante :
const [state, formAction, isPending] = useActionState(action, initialState);
action: La fonction d'action serveur à exécuter. Cette fonction reçoit l'état précédent et les données du formulaire en arguments.initialState: La valeur que vous souhaitez que l'état ait initialement, avant que l'action ne soit jamais appelée.state: L'état renvoyé par l'action après son achèvement. Au rendu initial, il s'agit de lainitialState.formAction: Une nouvelle action que vous transmettez à la propactionde votre élément<form>. Lorsque cette action est invoquée, elle déclenchera l'actiond'origine, mettra à jour l'indicateurisPendinget mettra à jour lestateavec le résultat.isPending: Un booléen qui esttruependant que l'action est en cours etfalsedans le cas contraire. Ceci est incroyablement utile pour désactiver les boutons de soumission ou afficher des indicateurs de chargement.
Bien que ce hook soit une primitive fantastique, sa véritable puissance est débloquée lorsque vous concevez un système robuste autour de lui.
Le défi : Au-delà de l'affichage simple des erreurs
L'implémentation la plus courante de la gestion des erreurs avec useActionState implique que l'action serveur renvoie un simple objet d'erreur, qui est ensuite affiché dans l'interface utilisateur. Par exemple :
// Une action serveur simple, mais limitée
export async function updateUser(prevState, formData) {
const name = formData.get('name');
if (name.length < 3) {
return { success: false, message: 'Name must be at least 3 characters long.' };
}
// ... mettre à jour l'utilisateur dans la base de données
return { success: true, message: 'Profile updated!' };
}
Cela fonctionne, mais cela a des limitations importantes qui conduisent à une mauvaise expérience utilisateur :
- Saisie utilisateur perdue : Lorsque le formulaire est soumis et qu'une erreur se produit, le navigateur affiche à nouveau la page avec le résultat rendu par le serveur. Si les champs de saisie ne sont pas contrôlés, toutes les données que l'utilisateur a saisies peuvent être perdues, ce qui l'oblige à recommencer. C'est une source majeure de frustration pour l'utilisateur.
- Aucun chemin de récupération clair : L'utilisateur voit un message d'erreur, mais quelle est la prochaine étape ? S'il y a plusieurs champs, il ne sait pas lequel est incorrect. S'il s'agit d'une erreur de serveur, il ne sait pas s'il doit réessayer maintenant ou plus tard.
- Incapacité à différencier les erreurs : L'erreur était-elle due à une saisie non valide (une erreur de niveau 400), à un plantage côté serveur (une erreur de niveau 500) ou à un échec d'authentification ? Une simple chaîne de message ne peut pas transmettre ce contexte, qui est essentiel pour créer des réponses d'interface utilisateur intelligentes.
Pour créer des applications professionnelles de qualité entreprise, nous avons besoin d'une approche plus structurée et résiliente.
Une stratégie robuste de récupération d'erreur avec `useActionState`
Notre stratégie repose sur trois piliers fondamentaux : une réponse d'action standardisée, une gestion intelligente de l'état côté client et une interface utilisateur centrée sur l'utilisateur qui guide la récupération.
Étape 1 : Définition d'une forme de réponse d'action standardisée
La cohérence est essentielle. La première étape consiste à établir un contrat : une structure de données cohérente que chaque action serveur renverra. Cette prévisibilité permet à nos composants frontend de gérer le résultat de toute action sans logique personnalisée pour chacune.
Voici une forme de réponse robuste qui peut gérer une variété de scénarios :
// Une définition de type pour notre réponse standardisée
interface ActionResponse {
success: boolean;
message?: string; // Pour un feedback global, visible par l'utilisateur (par exemple, les notifications toast)
errors?: Record | null; // Erreurs de validation spécifiques au champ
errorType?: 'VALIDATION' | 'SERVER_ERROR' | 'AUTH_ERROR' | 'NOT_FOUND' | null;
data?: T | null; // La charge utile en cas de succès
}
success: Un booléen clair indiquant le résultat.message: Un message global, lisible par l'homme. Ceci est parfait pour les toasts ou les bannières comme "Profil mis à jour avec succès !" ou "Impossible de se connecter au serveur".errors: Un objet où les clés correspondent aux noms des champs de formulaire (par exemple,'email') et les valeurs sont des tableaux de chaînes d'erreur. Cela permet d'afficher plusieurs erreurs par champ.errorType: Une chaîne de caractères de type énumération qui catégorise l'erreur. C'est le secret qui permet à notre interface utilisateur de réagir différemment aux différents modes de défaillance.data: La ressource créée ou mise à jour avec succès, qui peut être utilisée pour mettre à jour l'interface utilisateur ou rediriger l'utilisateur.
Exemple de réponse de succès :
{
success: true,
message: 'User profile updated successfully!',
data: { id: '123', name: 'John Doe', email: 'john.doe@example.com' }
}
Exemple de réponse d'erreur de validation :
{
success: false,
message: 'Please correct the errors below.',
errors: {
email: ['Please enter a valid email address.'],
password: ['Password must be at least 8 characters long.', 'Password must contain a number.']
},
errorType: 'VALIDATION'
}
Exemple de réponse d'erreur de serveur :
{
success: false,
message: 'An unexpected error occurred. Our team has been notified. Please try again later.',
errors: null,
errorType: 'SERVER_ERROR'
}
Étape 2 : Conception de l'état initial du composant
Avec notre forme de réponse définie, l'état initial transmis à useActionState doit la refléter. Cela garantit la cohérence des types et empêche les erreurs d'exécution d'accéder à des propriétés qui n'existent pas lors du rendu initial.
const initialState = {
success: false,
message: '',
errors: null,
errorType: null,
data: null
};
Étape 3 : Implémentation de l'action serveur
Maintenant, implémentons une action serveur qui respecte notre contrat. Nous utiliserons la bibliothèque de validation populaire zod pour démontrer comment gérer les erreurs de validation de manière propre.
'use server';
import { z } from 'zod';
// Définir le schéma de validation
const profileSchema = z.object({
name: z.string().min(3, { message: 'Name must be at least 3 characters long.' }),
email: z.string().email({ message: 'Please enter a valid email address.' }),
});
// L'action serveur respecte notre réponse standardisée
export async function updateUserProfileAction(previousState, formData) {
const validatedFields = profileSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
});
// Gérer les erreurs de validation
if (!validatedFields.success) {
return {
success: false,
message: 'Validation failed. Please check the fields.',
errors: validatedFields.error.flatten().fieldErrors,
errorType: 'VALIDATION',
data: null
};
}
try {
// Simuler une opération de base de données
console.log('Updating user:', validatedFields.data);
// const updatedUser = await db.user.update(...);
// Simuler une erreur de serveur potentielle
if (validatedFields.data.email.includes('fail')) {
throw new Error('Database connection failed');
}
return {
success: true,
message: 'Profile updated successfully!',
errors: null,
errorType: null,
data: validatedFields.data
};
} catch (error) {
console.error('Server Error:', error);
return {
success: false,
message: 'An internal server error occurred. Please try again later.',
errors: null,
errorType: 'SERVER_ERROR',
data: null
};
}
}
Cette action est maintenant une fonction prévisible et robuste. Elle sépare clairement la logique de validation de la logique métier et gère les erreurs inattendues avec élégance, en renvoyant toujours une réponse que notre frontend peut comprendre.
Construire l'UI : Une approche centrée sur l'utilisateur
Maintenant, passons à la partie la plus importante : utiliser cet état structuré pour créer une expérience utilisateur supérieure. Notre objectif est de guider l'utilisateur, pas seulement de le bloquer.
La configuration du composant de base
Configurons notre composant de formulaire. La clé pour préserver la saisie de l'utilisateur en cas d'échec est d'utiliser des composants contrôlés. Nous allons gérer l'état des entrées avec useState. Lorsque la soumission du formulaire échoue, le composant se rend à nouveau, mais comme les valeurs d'entrée sont conservées dans l'état React, elles ne sont pas perdues.
'use client';
import { useState } from 'react';
import { useActionState } from 'react';
import { updateUserProfileAction } from './actions';
const initialState = { success: false, message: '', errors: null, errorType: null };
export function UserProfileForm({ user }) {
const [state, formAction, isPending] = useActionState(updateUserProfileAction, initialState);
// Utiliser useState pour contrôler les entrées du formulaire et les préserver lors du nouveau rendu
const [name, setName] = useState(user.name);
const [email, setEmail] = useState(user.email);
return (
);
}
Principaux détails d'implémentation de l'interface utilisateur :
- Entrées contrôlées : En utilisant
useStatepournameetemail, les valeurs d'entrée sont gérées par React. Lorsque l'action serveur échoue et que le composant se rend à nouveau avec le nouvel état d'erreur, les variables d'étatnameetemailrestent inchangées, préservant ainsi parfaitement la saisie de l'utilisateur. C'est la technique la plus importante pour une bonne expérience de récupération. - Bannière de message globale : Nous utilisons
state.messagepour afficher un message de niveau supérieur. Nous pouvons même changer sa couleur en fonction destate.success. - Erreurs spécifiques au champ : Nous vérifions
state.errors?.fieldNameet, si elle est présente, affichons le message d'erreur directement sous l'entrée correspondante. - Accessibilité : Nous utilisons
aria-invalidpour indiquer par programmation aux lecteurs d'écran qu'un champ contient une erreur.aria-describedbyrelie l'entrée à son message d'erreur, garantissant que le texte d'erreur est lu lorsque l'utilisateur se concentre sur le champ non valide. - État en attente : Le booléen
isPendingest utilisé pour désactiver le bouton de soumission, empêchant les soumissions en double et fournissant un retour visuel clair qu'une opération est en cours.
Modèles de récupération avancés
Avec notre base solide, nous pouvons maintenant implémenter des expériences utilisateur plus avancées en fonction du type d'erreur.
Gestion des différents types d'erreurs
Notre champ errorType est maintenant incroyablement utile. Nous pouvons l'utiliser pour afficher des composants d'interface utilisateur complètement différents pour différents scénarios d'échec.
function ErrorRecoveryUI({ state, onRetry }) {
if (!state.errorType) return null;
switch (state.errorType) {
case 'VALIDATION':
// Pour la validation, le principal retour d'information est constitué des erreurs de champ en ligne,
// nous pourrions donc ne pas avoir besoin d'un composant spécial ici. Le message global est suffisant.
return Please review the fields marked in red.
;
case 'SERVER_ERROR':
return (
A Server Error Occurred
{state.message}
);
case 'AUTH_ERROR':
return (
);
default:
return {state.message}
;
}
}
// Dans le retour du composant principal :
Implémentation d'un mécanisme de "Réessai"
Pour les erreurs récupérables comme SERVER_ERROR, un bouton "Réessayer" est une excellente expérience utilisateur. Comment implémentons-nous cela ? Le `formAction` est lié à l'événement de soumission du formulaire. Une approche simple consiste à ce que le bouton "Réessayer" réinitialise l'état de l'action et réactive le formulaire, invitant l'utilisateur à cliquer à nouveau sur le bouton de soumission principal.
Étant donné que useActionState ne fournit pas de fonction `reset`, un modèle courant consiste à l'envelopper dans un hook personnalisé ou à le gérer en faisant en sorte que le composant se rend à nouveau avec une nouvelle clé, bien que l'approche la plus simple consiste souvent simplement à guider l'utilisateur.
Une solution pragmatique : la saisie de l'utilisateur est déjà préservée. L'indicateur `isPending` sera faux. Le meilleur "réessai" consiste simplement à permettre à l'utilisateur de cliquer à nouveau sur le bouton de soumission d'origine. L'interface utilisateur peut simplement les guider :
Pour une `SERVER_ERROR`, notre interface utilisateur peut afficher le message d'erreur : "Une erreur s'est produite. Vos modifications ont été enregistrées. Veuillez essayer de soumettre à nouveau." Le bouton de soumission est déjà activé car `isPending` est faux. Cela ne nécessite aucune gestion d'état complexe.
Combinaison avec `useOptimistic`
Pour une sensation encore plus réactive, useActionState se marie à merveille avec le hook useOptimistic. Vous pouvez supposer que l'action réussira et mettre à jour l'interface utilisateur instantanément. Si l'action échoue, useActionState recevra l'état d'erreur, ce qui déclenchera un nouveau rendu et rétablira automatiquement la mise à jour optimiste à l'état réel.
Cela dépasse le cadre de cet examen approfondi de la gestion des erreurs, mais c'est la prochaine étape logique dans la création d'expériences utilisateur vraiment modernes avec les Actions React.
Considérations globales pour les applications internationales
Lorsque vous construisez pour un public mondial, le codage en dur des messages d'erreur en anglais n'est pas une option viable.
Internationalisation (i18n)
Notre structure de réponse standardisée peut être facilement adaptée à l'internationalisation. Au lieu de renvoyer une chaîne `message` codée en dur, le serveur doit renvoyer une clé ou un code de message.
Réponse du serveur modifiée :
{
success: false,
messageKey: 'errors.validation.checkFields',
errors: {
email: ['errors.validation.email.invalid'],
},
errorType: 'VALIDATION'
}
Sur le client, vous utiliseriez une bibliothèque comme react-i18next ou react-intl pour traduire ces clés dans la langue sélectionnée par l'utilisateur.
import { useTranslation } from 'react-i18next';
// À l'intérieur de votre composant
const { t } = useTranslation();
// ...
{state.messageKey && {t(state.messageKey)}
}
// ...
{state.errors?.email && {t(state.errors.email[0])}
}
Cela découple votre logique d'action de la couche de présentation, ce qui facilite la maintenance et la traduction de votre application dans de nouvelles langues.
Conclusion
Le hook useActionState est plus qu'une simple commodité ; c'est un élément fondamental pour la construction d'applications web modernes et résilientes dans React. En allant au-delà de l'affichage de base des messages d'erreur et en adoptant une stratégie complète de récupération d'erreur, vous pouvez considérablement améliorer l'expérience utilisateur.
Récapitulons les principes clés de notre stratégie :
- Standardisez la réponse de votre serveur : Créez une structure JSON cohérente pour toutes vos actions. Ce contrat est le fondement d'un comportement frontend prévisible. Incluez un
errorTypedistinct pour différencier les modes de défaillance. - Préservez la saisie de l'utilisateur à tout prix : Utilisez des composants contrôlés (
useState) pour gérer les valeurs des champs de formulaire. Cela empêche la perte de données en cas d'échec de la soumission et constitue la pierre angulaire d'une expérience utilisateur indulgente. - Fournissez un feedback contextuel : Utilisez votre état d'erreur structuré pour afficher des messages globaux, des erreurs de champ en ligne et une interface utilisateur personnalisée pour différents types d'erreurs (par exemple, les erreurs de validation par rapport aux erreurs de serveur).
- Construisez pour un public mondial : Dissociez les messages d'erreur de votre logique serveur à l'aide de clés d'internationalisation, et tenez toujours compte des normes d'accessibilité (attributs ARIA) pour vous assurer que vos formulaires sont utilisables par tous.
En investissant dans une stratégie robuste de gestion des erreurs, vous ne vous contentez pas de corriger des bogues, vous établissez une relation de confiance avec vos utilisateurs. Vous créez des applications qui se sentent stables, professionnelles et respectueuses de leur temps et de leurs efforts. Au fur et à mesure que vous continuez à construire avec React Actions, laissez ce cadre vous guider dans la création d'expériences qui ne sont pas seulement fonctionnelles, mais vraiment agréables à utiliser, quel que soit l'endroit où se trouvent vos utilisateurs dans le monde.