Valider des formulaires avec React Hook Form et Zod
Ce guide vous explique comment valider des formulaires en utilisant React Hook Form et Zod .
Qu’est-ce que React Hook Form?
React Hook Form (RHF) est une bibliothèque conçue pour gérer et valider des formulaires dans les applications React. En utilisant les hooks de React, RHF offre une méthode simple et efficace pour gérer l’état du formulaire, la validation et la soumission. Voici quelques avantages clés de l’utilisation de RHF:
- Re-rendu minimal: Améliore les performances en réduisant les re-rendus inutiles.
- Intégration facile: Fonctionne parfaitement avec diverses bibliothèques d’UI comme Carbon et Material-UI.
- Amélioration des performances: Utilise des composants non contrôlés pour booster les performances des formulaires.
Qu’est-ce que Zod?
Zod est une bibliothèque de déclaration et de validation de schéma orientée TypeScript. Elle offre un moyen intuitif de définir des schémas de données et de valider ces données par rapport à ces schémas. Les caractéristiques clés de Zod incluent:
- Sécurité de type: Assure la sécurité des types, ce qui en fait un choix parfait pour les projets TypeScript.
- Facilité d’utilisation: Fournit une API simple pour définir et valider des schémas.
- Performances: Conçu pour être rapide et efficace.
Pourquoi les utiliser ensemble ?
Combiner RHF et Zod vous permet de tirer parti des forces des deux bibliothèques, créant ainsi un système de validation de formulaire robuste. RHF gère l’état du formulaire et le processus de soumission, tandis que Zod valide les données du formulaire par rapport aux schémas prédéfinis, garantissant l’intégrité des données et la sécurité des types.
Exemple
L’exemple ci-dessous part d’un formulaire Carbon contrôlé, puis le réécrit avec React Hook Form et Zod:
<Form>
<ModalHeader closeModal={close} title={t("changePassword", "Changer le mot de passe")} />
<ModalBody>
<Stack gap={5}>
<PasswordInput
id="oldPassword"
labelText={t("oldPassword", "Ancien mot de passe")}
onChange={handleOldPasswordChange}
value={oldPassword}
/>
<PasswordInput
id="newPassword"
labelText={t("newPassword", "Nouveau mot de passe")}
onChange={handleNewPasswordChange}
value={newPassword}
/>
<PasswordInput
id="passwordConfirmation"
labelText={t("confirmPassword", "Confirmer le nouveau mot de passe")}
onChange={handlePasswordConfirmationChange}
value={passwordConfirmation}
/>
</Stack>
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={close}>
{t("cancel", "Annuler")}
</Button>
<Button className={styles.submitButton} onClick={handleSubmit} disabled={isChangingPassword} type="submit">
{isChangingPassword ? (
<InlineLoading description={t("changingPassword", "Changement du mot de passe") + "..."} />
) : (
<span>{t("change", "Changer")}</span>
)}
</Button>
</ModalFooter>
</Form>Ceci est un extrait d’un formulaire permettant aux utilisateurs de changer leur mot de passe. Le formulaire se compose de trois champs de saisie de mot de passe: oldPassword, newPassword et passwordConfirmation. Nous voulons valider ces champs en utilisant Zod afin que:
- Tous les champs soient obligatoires.
- La valeur du champ
newPassworddoit correspondre à la valeur du champpasswordConfirmation.
Étape 1
Installez les packages requis dans votre module frontend si le package ne les déclare pas déjà:
yarn add react-hook-form zod @hookform/resolversÉtape 2
Définir le schéma Zod pour les données du formulaire:
import { z } from "zod";
const oldPasswordValidation = z
.string({
required_error: t('oldPasswordRequired', 'Old password is required'),
})
.trim()
.min(1, t('oldPasswordRequired', 'Old password is required'));
const newPasswordValidation = z
.string({
required_error: t('newPasswordRequired', 'New password is required'),
})
.trim()
.min(1, t('newPasswordRequired', 'New password is required'));
const passwordConfirmationValidation = z
.string({
required_error: t('passwordConfirmationRequired', 'Password confirmation is required'),
})
.trim()
.min(1, t('passwordConfirmationRequired', 'Password confirmation is required'));
const changePasswordFormSchema = z
.object({
oldPassword: oldPasswordValidation,
newPassword: newPasswordValidation,
passwordConfirmation: passwordConfirmationValidation,
})
.refine((data) => data.newPassword === data.passwordConfirmation, {
message: t("passwordsDoNotMatch", "Passwords do not match"),
path: ["passwordConfirmation"],
});Ce schéma définit les règles de validation pour les champs du formulaire. Chaque champ est défini comme une chaîne de caractères qui ne doit pas être vide. Nous tronquons la chaîne pour supprimer les espaces en début et en fin. Si le champ est vide, un message d’erreur personnalisé est fourni en utilisant la fonction de traduction t(). De plus, une règle de raffinement est ajoutée pour vérifier si le newPassword correspond à la passwordConfirmation. Si elles ne correspondent pas, un message d’erreur est affiché. Ces règles de validation sont assez basiques - elles garantissent uniquement que chaque champ n’est pas vide. Elles n’imposent pas d’autres règles, telles que la complexité du mot de passe ou la longueur minimale.
Étape 3
Configurer React Hook Form pour utiliser le résolveur Zod:
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, type SubmitHandler } from "react-hook-form";
const {
handleSubmit,
control,
formState: { errors },
} = useForm<z.infer<typeof changePasswordFormSchema>>({
resolver: zodResolver(changePasswordFormSchema),
defaultValues: {
oldPassword: "",
newPassword: "",
passwordConfirmation: "",
},
});Ce code utilise zodResolver de @hookform/resolvers pour valider les données du formulaire avec le schéma Zod défini à l’étape 2. La propriété defaultValues est optionnelle mais recommandée pour s’assurer que les champs du formulaire sont initialisés avec des chaînes vides. Le paramètre de type générique <z.infer<typeof changePasswordFormSchema>> fournit l’inférence de type pour les données du formulaire.
Étape 4
Ajouter la fonction handleSubmit à votre formulaire:
import type { SubmitHandler } from "react-hook-form";
const onSubmit: SubmitHandler<z.infer<typeof changePasswordFormSchema>> = async (data) => {
const { oldPassword, newPassword } = data;
try {
setIsChangingPassword(true);
await changePassword(oldPassword, newPassword);
// Gérer le succès (par exemple, afficher un message de succès, fermer la modale)
} catch (error) {
// Gérer l'erreur (par exemple, afficher un message d'erreur)
console.error(error);
} finally {
setIsChangingPassword(false);
}
};
// Optionnel: Gérer les erreurs de validation
const onError = () => {
setIsChangingPassword(false);
};Le type SubmitHandler de React Hook Form offre une meilleure sécurité de type et garantit que le gestionnaire reçoit le bon type de données. Le callback onError est optionnel mais utile pour gérer les cas où la validation échoue.
Étape 5
Ajouter les composants Form et Controller à votre formulaire:
import { Controller } from "react-hook-form";
<Form onSubmit={handleSubmit(onSubmit, onError)}>
<Controller
name="oldPassword"
control={control}
render={({ field: { onChange, value } }) => (
<PasswordInput
id="oldPassword"
invalid={!!errors?.oldPassword}
invalidText={errors?.oldPassword?.message}
labelText={t("oldPassword", "Ancien mot de passe")}
onChange={onChange}
value={value}
/>
)}
/>
<Controller
name="newPassword"
control={control}
render={({ field: { onChange, value } }) => (
<PasswordInput
id="newPassword"
invalid={!!errors?.newPassword}
invalidText={errors?.newPassword?.message}
labelText={t("newPassword", "Nouveau mot de passe")}
onChange={onChange}
value={value}
/>
)}
/>
<Controller
name="passwordConfirmation"
control={control}
render={({ field: { onChange, value } }) => (
<PasswordInput
id="passwordConfirmation"
invalid={!!errors?.passwordConfirmation}
invalidText={errors?.passwordConfirmation?.message}
labelText={t("confirmPassword", "Confirmer le nouveau mot de passe")}
onChange={onChange}
value={value}
/>
)}
/>
<Button type="submit">{t("submit", "Soumettre")}</Button>
</Form>;Dans cet extrait de code, le composant Controller est utilisé pour connecter les champs du formulaire à React Hook Form. La prop name spécifie le nom du champ, la prop control spécifie le contrôle du formulaire, et la prop render spécifie le composant de saisie à rendre. Les props invalid et invalidText sont utilisées pour afficher les erreurs de validation pour chaque champ.
La fonction handleSubmit accepte deux callbacks: onSubmit (appelé lorsque la validation réussit) et onError (appelé lorsque la validation échoue). Cela vous permet de gérer à la fois les soumissions réussies et les erreurs de validation de manière appropriée.
Étape 6
C’est tout! Vous avez intégré avec succès React Hook Form avec Zod pour valider votre formulaire. Désormais, lorsque l’utilisateur soumet le formulaire, les données seront validées par rapport au schéma Zod, et toutes les erreurs de validation seront affichées à l’utilisateur.
Définir des schémas en dehors des composants
Dans certains cas, vous devrez peut-être définir votre schéma en dehors du composant. Cela inclut notamment:
- Le partage de logique de validation entre plusieurs formulaires.
- Des règles de validation qui doivent être dynamiques selon le contexte.
- Les formulaires en plusieurs étapes.
- La validation conditionnelle complexe.
Dans ces scénarios, vous pouvez créer une fonction factory qui retourne le schéma. Par exemple:
import { z } from "zod";
import { useMemo } from "react";
import type { TFunction } from "i18next";
const createChangePasswordSchema = (t: TFunction) => {
const requiredString = (key: string, defaultValue: string) =>
z
.string({
required_error: t(key, defaultValue),
})
.trim()
.min(1, t(key, defaultValue));
return z
.object({
oldPassword: requiredString("oldPasswordRequired", "Old password is required"),
newPassword: requiredString("newPasswordRequired", "New password is required"),
passwordConfirmation: requiredString("passwordConfirmationRequired", "Password confirmation is required"),
})
.refine((data) => data.newPassword === data.passwordConfirmation, {
message: t("passwordsDoNotMatch", "Passwords do not match"),
path: ["passwordConfirmation"],
});
};
// Définit un type pour les données du formulaire. Ce type peut être exporté et utilisé dans d'autres composants qui réutilisent le même schéma.
export type ChangePasswordFormData = z.infer<ReturnType<typeof createChangePasswordSchema>>;
// Plus bas dans le composant
const ChangePasswordModal = () => {
const { t } = useTranslation();
const changePasswordSchema = useMemo(() => createChangePasswordSchema(t), [t]);
const {
handleSubmit,
control,
formState: { errors },
} = useForm<ChangePasswordFormData>({
resolver: zodResolver(changePasswordSchema),
});
};Un avantage important de cette approche est que vous pouvez accéder à la fonction t depuis le corps du composant via la factory. Cela vous permet de traduire les messages d’erreur même si le schéma est défini en dehors du composant. Le principal compromis est une configuration initiale légèrement plus complexe, mais cela vaut généralement le coût pour la maintenabilité et la réutilisation.
En combinant React Hook Form et Zod, vous pouvez créer un puissant système de validation de formulaires qui garantit l’intégrité des données et la sécurité des types dans vos applications React. Lisez-en plus sur React Hook Form et Zod pour explorer toutes leurs capacités et fonctionnalités.
Ressources supplémentaires
- Cours gratuit sur Zod par Total TypeScript: Zod - Le Guide Ultime
- Corriger le point aveugle de TypeScript par Jack Herrington