Valider ses formulaires avec Zod et react-hook-form

React Hook Form est une bibliothèque polyvalente et efficace conçue pour gérer les formulaires dans les applications React.

··14 min. read
Share on TwitterShare on Linkedin
Cover Image for Valider ses formulaires avec Zod et react-hook-form

React Hook Form est une bibliothèque polyvalente et efficace conçue pour gérer les formulaires dans les applications React. Son utilisation est à la fois flexible et simple. En revanche, Zod se distingue comme une bibliothèque orientée TypeScript principalement axée sur la déclaration et la validation des schémas.

Cet article vous guide à travers le processus de construction d'un formulaire d'inscription de base en utilisant React Hook Form et Zod comme bibliothèque de validation. Le tutoriel incorpore également TypeScript et TailwindCSS pour le style, bien que les implémentations de style restent rudimentaires.

Installation

Nous utiliserons Vite avec TypeScript pour créer notre projet. Vous pouvez utiliser n'importe laquelle des commandes suivantes :

npm create vite@latest
or
yarn create vite
or 
pnpm create vite

Ensuite, nous procédons à l'installation de TailwindCSS. Le processus d'installation de TailwindCSS dans ce projet est conforme à la configuration standard décrite dans le guide ["Installer Tailwind CSS avec Vite"] (https://tailwindcss.com/docs/guides/vite). Cliquez sur le lien fourni, suivez méticuleusement les instructions et revenez à ce point une fois l'installation terminée.

Nous allons intégrer React Hook Form, Zod et @hookform/resolvers. L'inclusion de @hookform/resolvers facilite l'utilisation de bibliothèques de validation externes, comme c'est le cas avec Zod dans notre projet.

npm install react-hook-form @hookform/resolvers zod

Et c'est tout ! Nous avons installé toutes les dépendances nécessaires au projet.

Tout d'abord, créons un nouveau composant appelé Form

import React from "react";

const Form = () => {
  return (
    <form className="px-8 pt-6 pb-8 mb-4">
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="firstName"
          >
            First Name
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="firstName"
            type="text"
            placeholder="First Name"
          />
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="lastName"
          >
            Last Name
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="lastName"
            type="text"
            placeholder="Last Name"
          />
        </div>
      </div>
      <div className="mb-4">
        <label
          className="block mb-2 text-sm font-bold text-gray-700"
          htmlFor="email"
        >
          Email
        </label>
        <input
          className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
          id="email"
          type="email"
          placeholder="Email"
        />
      </div>
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="password"
          >
            Password
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="password"
            type="password"
          />
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="c_password"
          >
            Confirm Password
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="c_password"
            type="password"
          />
        </div>
      </div>
      <div className="mb-4">
        <input type="checkbox" id="terms" />
        <label
          htmlFor="terms"
          className="ml-2 mb-2 text-sm font-bold text-gray-700"
        >
          Accept Terms & Conditions
        </label>
      </div>
      <div className="mb-6 text-center">
        <button
          className="w-full px-4 py-2 font-bold text-white bg-blue-500 rounded-full hover:bg-blue-700 focus:outline-none focus:shadow-outline"
          type="submit"
        >
          Register Account
        </button>
      </div>
      <hr className="mb-6 border-t" />
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="#"
        >
          Forgot Password?
        </a>
      </div>
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="#"
        >
          Already have an account? Login!
        </a>
      </div>
    </form>
  );
};

export default Form;

Nous pouvons maintenant importer Form dans notre App.tsx


import React from "react";
import Form from "./Form";

function App() {
  return (
    <div className="max-w-xl mx-auto w-full">
      <div className="flex justify-center my-12">
        <div className="w-full lg:w-11/12 bg-white p-5 rounded-lg shadow-xl">
          <h3 className="pt-4 text-2xl text-center font-bold">
            Create New Account
          </h3>
          <Form />
        </div>
      </div>
    </div>
  );
}

export default App;

Schéma de validation utilisant Zod

Zod fonctionne comme une bibliothèque de déclaration et de validation de schémas centrée sur TypeScript. L'approche "TypeScript-first" implique que Zod déduit de manière transparente le type TypeScript statique pour votre structure de données, ce qui vous oblige à déclarer un validateur une seule fois. Cela élimine le besoin de déclarations de type redondantes - d'abord dans Zod, puis à nouveau dans TypeScript. L'utilisation de z.infer<typeof schema> avec le mot-clé "infer" vous permet d'extraire automatiquement le type d'un schéma. Cette fonctionnalité s'avère particulièrement avantageuse pour les utilisateurs de TypeScript, car elle simplifie le processus de typage.

Cela fonctionne comme cela :

import { zod } from "Zod";
const personSchema = z.object({
    name: z.string(),
    age: z.number()
});
// extracting the type
type Person = z.infer<typeof personSchema>;

Importons les dépendances nécessaires dans src/Form.tsx

import { SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

Ensuite, nous allons articuler notre schéma, validationSchema, en incorporant des messages d'erreur personnalisés. Cette étape se déroule dans le fichier src/Form.tsx, entre la déclaration d'importation et notre composant Form.

const validationSchema = z
  .object({
    firstName: z.string().min(1, { message: "Firstname is required" }),
    lastName: z.string().min(1, { message: "Lastname is required" }),
    email: z.string().min(1, { message: "Email is required" }).email({
      message: "Must be a valid email",
    }),
    password: z
      .string()
      .min(6, { message: "Password must be atleast 6 characters" }),
    confirmPassword: z
      .string()
      .min(1, { message: "Confirm Password is required" }),
    terms: z.literal(true, {
      errorMap: () => ({ message: "You must accept Terms and Conditions" }),
    }),
  })
  .refine((data) => data.password === data.confirmPassword, {
    path: ["confirmPassword"],
    message: "Password don't match",
  });

Déconstruisons le code étape par étape.

Nous lançons la déclaration de validationSchema à l'aide de la méthode z.object(). Plus précisément, pour les champs firstname et lastname, nous utilisons une validation de chaîne couplée à une validation de longueur minimale en utilisant z.string().min(1, { message : 'Firstname is required'}) Cette construction garantit que les données saisies ne sont pas seulement une chaîne de caractères, mais qu'elles comportent également un ou plusieurs caractères, ce qui rend le champ obligatoire. Des messages d'erreur personnalisés, tels que "Le prénom est obligatoire", peuvent être attribués lors de l'utilisation des méthodes de validation. Le processus d'affichage de ces messages d'erreur sera abordé plus loin dans notre exploration.

En ce qui concerne le champ de l'adresse électronique, nous utilisons la validation des chaînes de caractères, la validation de la longueur minimale et la validation de l'adresse électronique. Zod propose plusieurs validations spécifiques aux chaînes de caractères, dont l'option "email".

Pour le champ mot de passe, la validation commence par une validation de chaîne, suivie d'une vérification que le mot de passe saisi doit être long de 6 caractères ou plus. Quant au champ confirmPassword, nous le validons de la même manière que les champs firstname et lastname. En outre, nous devons nous assurer que le mot de passe et le confirmPassword correspondent. Zod permet d'incorporer une logique de validation personnalisée par le biais de raffinements, en particulier la méthode refine.

.refine((data) => data.password === data.confirmPassword, {
  path: ["confirmPassword"], // path of error
  message: "Password don't match",
});

Voici maintenant notre composant Form.tsx :

import { SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const validationSchema = z
  .object({
    firstName: z.string().min(1, { message: "Firstname is required" }),
    lastName: z.string().min(1, { message: "Lastname is required" }),
    email: z.string().min(1, { message: "Email is required" }).email({
      message: "Must be a valid email",
    }),
    password: z
      .string()
      .min(6, { message: "Password must be atleast 6 characters" }),
    confirmPassword: z
      .string()
      .min(1, { message: "Confirm Password is required" }),
    terms: z.literal(true, {
      errorMap: () => ({ message: "You must accept Terms and Conditions" }),
    }),
  })
  .refine((data) => data.password === data.confirmPassword, {
    path: ["confirmPassword"],
    message: "Password don't match",
  });

type ValidationSchema = z.infer<typeof validationSchema>;

const Form = () => {
  return (
    <form className="px-8 pt-6 pb-8 mb-4">
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="firstName"
          >
            First Name
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="firstName"
            type="text"
            placeholder="First Name"
          />
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="lastName"
          >
            Last Name
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="lastName"
            type="text"
            placeholder="Last Name"
          />
        </div>
      </div>
      <div className="mb-4">
        <label
          className="block mb-2 text-sm font-bold text-gray-700"
          htmlFor="email"
        >
          Email
        </label>
        <input
          className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
          id="email"
          type="email"
          placeholder="Email"
        />
      </div>
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="password"
          >
            Password
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="password"
            type="password"
          />
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="c_password"
          >
            Confirm Password
          </label>
          <input
            className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
            id="c_password"
            type="password"
          />
        </div>
      </div>
      <div className="mb-4">
        <input type="checkbox" id="terms" />
        <label
          htmlFor="terms"
          className="ml-2 mb-2 text-sm font-bold text-gray-700"
        >
          Accept Terms & Conditions
        </label>
      </div>
      <div className="mb-6 text-center">
        <button
          className="w-full px-4 py-2 font-bold text-white bg-blue-500 rounded-full hover:bg-blue-700 focus:outline-none focus:shadow-outline"
          type="submit"
        >
          Register Account
        </button>
      </div>
      <hr className="mb-6 border-t" />
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="#"
        >
          Forgot Password?
        </a>
      </div>
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="#"
        >
          Already have an account? Login!
        </a>
      </div>
    </form>
  );
};

export default Form;


React hook form

Commençons par l'importer :

import { SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

Nous ajouterons l'extrait de code suivant au composant Form.tsx :


  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<ValidationSchema>({
    resolver: zodResolver(validationSchema),
  });

  const onSubmit: SubmitHandler<ValidationSchema> = (data) => console.log(data);

Le hook useForm est un utilitaire personnalisé qui simplifie la gestion des formulaires. Il accepte un objet optionnel comme argument, et dans notre cas, nous utiliserons la propriété resolver. Cette propriété permet d'utiliser n'importe quelle bibliothèque de validation externe, comme Zod. Nous employons le hook zodResolver de @hookform/resolvers/zod, en lui passant le validateur validationSchema. Il est important de noter que nous avons assigné des types déduits par Zod à useForm et SubmitHandler.

Nous avons défini la fonction onSubmit, qui va enregistrer les données validées dans la console. La fonction onSubmit ne sera invoquée qu'une fois que la fonction handleSubmit, fournie par useForm, aura validé les entrées. A ce stade, nous avons mis en place ces fonctions et d'autres éléments essentiels, y compris register et formState, avec le hook useForm.

Cependant, il est crucial d'observer qu'essayer de remplir le formulaire et de le soumettre à ce stade ne donne aucune réponse. C'est parce que nous n'avons pas enregistré les entrées du formulaire, et que nous n'avons pas utilisé la fonction onSubmit pour gérer l'événement de soumission.

Les champs

L'un des concepts clés de React Hook Form est d'enregistrer votre composant dans le hook. Cela rendra sa valeur disponible à la fois pour la validation et la soumission du formulaire.

Note : Chaque champ doit avoir un nom comme clé pour le processus d'enregistrement.

Nous allons utiliser la fonction register que nous obtenons du hook useForm pour enregistrer les entrées du formulaire, en leur donnant le même nom que celui que nous avons fourni au validationSchema.

Pour le champ firstname, nous l'enregistrerons comme suit :

<input 
  className="w-full px-3 py-2 text-sm leading-tight text-gray-700        border rounded appearance-none focus:outline-none focus:shadow-  outline"            
  id="firstName"            
  type="text"            
  placeholder="First Name"  
  {...register("firstName")}        
/>

Il en va de même pour le champ de l'email :

<input          
  className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"          
  id="email"          
  type="email"          
  placeholder="Email"
  {...register("email")}       
/>

etc...

La soumission du formulaire

Nous avons déjà ajouté la fonction de gestion personnalisée onSubmit. Tout ce que nous avons à faire est de passer la fonction onSubmit à la fonction handleSubmit en tant qu'argument et le reste est pris en charge par celle-ci.

<form className="px-8 pt-6 pb-8 mb-4" onSubmit= {handleSubmit(onSubmit)}>

Nous obtenons la méthode handleSubmit à partir du hook useForm, ce qui signifie que handleSubmit validera les entrées avant d'invoquer la méthode personnalisée onSubmit. Notez que nous n'avons pas besoin d'empêcher le comportement par défaut comme nous le ferions normalement, cela est fait par la méthode handleSubmit.

Maintenant, si vous essayez de remplir le formulaire avec des données valides et que vous le soumettez, la valeur est stockée dans la console avec succès. Cependant, si vous essayez de soumettre le formulaire avec des valeurs non valides ou avec un formulaire vide, rien ne se passe. Nous devrons afficher conditionnellement le message d'erreur pour chaque champ s'il est invalide.

Gérer les erreurs

React Hook Form propose un objet errors pour faciliter l'affichage des erreurs dans le formulaire. Le type d'erreur correspond aux contraintes de validation spécifiées. Pour accéder à cette information, on peut déstructurer le formState pour récupérer l'objet errors.

const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm<ValidationSchema>({
  resolver: zodResolver(validationSchema),
});

Nous pouvons maintenant mettre en œuvre l'affichage conditionnel des messages d'erreur pour chaque champ à l'aide de l'objet errors. Plus précisément, pour le champ firstName :

<input
  className="w-full px-3 py-2 text-sm leading-tight text-gray-700 border rounded appearance-none focus:outline-none focus:shadow-outline"
  id="firstName"
  type="text"
  {...register("firstName")}
/>
{errors.firstName && (
  <p className="text-xs italic text-red-500 mt-2"> {errors.firstName?.message}
  </p>
)}

Pour les autres champs, vous pouvez les comparer ci-dessous :

<form className="px-8 pt-6 pb-8 mb-4" onSubmit={handleSubmit(onSubmit)}>
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="firstName"
          >
            First Name
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.firstName && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="firstName"
            type="text"
            placeholder="First Name"
            {...register("firstName")}
          />
          {errors.firstName && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.firstName?.message}
            </p>
          )}
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="lastName"
          >
            Last Name
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.lastName && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="lastName"
            type="text"
            placeholder="Last Name"
            {...register("lastName")}
          />
          {errors.lastName && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.lastName?.message}
            </p>
          )}
        </div>
      </div>
      <div className="mb-4">
        <label
          className="block mb-2 text-sm font-bold text-gray-700"
          htmlFor="email"
        >
          Email
        </label>
        <input
          className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
            errors.email && "border-red-500"
          } rounded appearance-none focus:outline-none focus:shadow-outline`}
          id="email"
          type="email"
          placeholder="Email"
          {...register("email")}
        />
        {errors.email && (
          <p className="text-xs italic text-red-500 mt-2">
            {errors.email?.message}
          </p>
        )}
      </div>
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="password"
          >
            Password
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.password && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="password"
            type="password"
            {...register("password")}
          />
          {errors.password && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.password?.message}
            </p>
          )}
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="c_password"
          >
            Confirm Password
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.confirmPassword && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="c_password"
            type="password"
            {...register("confirmPassword")}
          />
          {errors.confirmPassword && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.confirmPassword?.message}
            </p>
          )}
        </div>
      </div>
      <div className="mb-4">
        <input type="checkbox" id="terms" {...register("terms")} />
        <label
          htmlFor="terms"
          className={`ml-2 mb-2 text-sm font-bold ${
            errors.terms ? "text-red-500" : "text-gray-700"
          }`}
        >
          Accept Terms & Conditions
        </label>
        {errors.terms && (
          <p className="text-xs italic text-red-500 mt-2">
            {errors.terms?.message}
          </p>
        )}
      </div>
      <div className="mb-6 text-center">
        <button
          className="w-full px-4 py-2 font-bold text-white bg-blue-500 rounded-full hover:bg-blue-700 focus:outline-none focus:shadow-outline"
          type="submit"
        >
          Register Account
        </button>
      </div>
      <hr className="mb-6 border-t" />
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="#test"
        >
          Forgot Password?
        </a>
      </div>
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="./index.html"
        >
          Already have an account? Login!
        </a>
      </div>
    </form>

En outre, j'ai incorporé ${errors.firstName && "border-red-500"} dans le nom de classe pour mettre en évidence de manière dynamique l'entrée avec une bordure rouge lorsqu'elle n'est pas valide. Maintenant, si vous essayez de soumettre le formulaire avec des entrées invalides, vous devriez voir les messages d'erreur apparaître dans les champs correspondants.

Pour récapituler les progrès réalisés dans le fichier Form.tsx jusqu'à présent :


import { SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const validationSchema = z
  .object({
    firstName: z.string().min(1, { message: "Firstname is required" }),
    lastName: z.string().min(1, { message: "Lastname is required" }),
    email: z.string().min(1, { message: "Email is required" }).email({
      message: "Must be a valid email",
    }),
    password: z
      .string()
      .min(6, { message: "Password must be atleast 6 characters" }),
    confirmPassword: z
      .string()
      .min(1, { message: "Confirm Password is required" }),
    terms: z.literal(true, {
      errorMap: () => ({ message: "You must accept Terms and Conditions" }),
    }),
  })
  .refine((data) => data.password === data.confirmPassword, {
    path: ["confirmPassword"],
    message: "Password don't match",
  });

type ValidationSchema = z.infer<typeof validationSchema>;

const Form = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<ValidationSchema>({
    resolver: zodResolver(validationSchema),
  });

  const onSubmit: SubmitHandler<ValidationSchema> = (data) => console.log(data);

  return (
    <form className="px-8 pt-6 pb-8 mb-4" onSubmit={handleSubmit(onSubmit)}>
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="firstName"
          >
            First Name
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.firstName && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="firstName"
            type="text"
            placeholder="First Name"
            {...register("firstName")}
          />
          {errors.firstName && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.firstName?.message}
            </p>
          )}
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="lastName"
          >
            Last Name
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.lastName && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="lastName"
            type="text"
            placeholder="Last Name"
            {...register("lastName")}
          />
          {errors.lastName && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.lastName?.message}
            </p>
          )}
        </div>
      </div>
      <div className="mb-4">
        <label
          className="block mb-2 text-sm font-bold text-gray-700"
          htmlFor="email"
        >
          Email
        </label>
        <input
          className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
            errors.email && "border-red-500"
          } rounded appearance-none focus:outline-none focus:shadow-outline`}
          id="email"
          type="email"
          placeholder="Email"
          {...register("email")}
        />
        {errors.email && (
          <p className="text-xs italic text-red-500 mt-2">
            {errors.email?.message}
          </p>
        )}
      </div>
      <div className="mb-4 md:flex md:justify-between">
        <div className="mb-4 md:mr-2 md:mb-0">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="password"
          >
            Password
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.password && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="password"
            type="password"
            {...register("password")}
          />
          {errors.password && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.password?.message}
            </p>
          )}
        </div>
        <div className="md:ml-2">
          <label
            className="block mb-2 text-sm font-bold text-gray-700"
            htmlFor="c_password"
          >
            Confirm Password
          </label>
          <input
            className={`w-full px-3 py-2 text-sm leading-tight text-gray-700 border ${
              errors.confirmPassword && "border-red-500"
            } rounded appearance-none focus:outline-none focus:shadow-outline`}
            id="c_password"
            type="password"
            {...register("confirmPassword")}
          />
          {errors.confirmPassword && (
            <p className="text-xs italic text-red-500 mt-2">
              {errors.confirmPassword?.message}
            </p>
          )}
        </div>
      </div>
      <div className="mb-4">
        <input type="checkbox" id="terms" {...register("terms")} />
        <label
          htmlFor="terms"
          className={`ml-2 mb-2 text-sm font-bold ${
            errors.terms ? "text-red-500" : "text-gray-700"
          }`}
        >
          Accept Terms & Conditions
        </label>
        {errors.terms && (
          <p className="text-xs italic text-red-500 mt-2">
            {errors.terms?.message}
          </p>
        )}
      </div>
      <div className="mb-6 text-center">
        <button
          className="w-full px-4 py-2 font-bold text-white bg-blue-500 rounded-full hover:bg-blue-700 focus:outline-none focus:shadow-outline"
          type="submit"
        >
          Register Account
        </button>
      </div>
      <hr className="mb-6 border-t" />
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="#test"
        >
          Forgot Password?
        </a>
      </div>
      <div className="text-center">
        <a
          className="inline-block text-sm text-blue-500 align-baseline hover:text-blue-800"
          href="./index.html"
        >
          Already have an account? Login!
        </a>
      </div>
    </form>
  );
};

export default Form;

Conclusion

Dans cet article, nous avons exploré comment utiliser Zod comme bibliothèque de validation avec React Hook Form. Nous avons approfondi la création d'un schéma et son utilisation pour déduire des types pour notre formulaire. J'ai cherché à couvrir les fondamentaux des deux bibliothèques et comment elles peuvent s'intégrer l'une à l'autre de manière transparente.

Share on TwitterShare on Linkedin