Comment ajouter des validateurs custom sur react-hook-form

Comment ajouter des validateurs custom sur react-hook-form

Alexandre P. dans Dev - Le 17-04-2024

Vous est-il déjà arrivé de galérer à ajouter des tests sur vos formulaires avec React-Hook-Form ? Pour peu que vous ayez un test custom à mettre en place, que ce soit avec Yup et la fonction .test ou avec Zod et son .refine. Dans cet article nous allons enfin régler ce problème de formState.

Le formState de react-hook-form ne se met jamais à jour

const {
    register,
    handleSubmit,
    formState
  } = useForm<PostForm>({
    resolver: zodResolver(postValidationSchema)
  })

Vous est-il déjà arrivé d'avoir errors toujours vide avec votre resolver ?

Lorsque vous loggez formState dans un useEffect.

Commençons par vérifier notre resolver zod

Voici à quoi ressemble mon formulaire:

zod_form.png

Voici le schéma zod:

import { ZodType, z } from "zod"

const isUrl =
  /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/

export const postValidationSchema: ZodType<PostForm> = z
  .object({
    videoUrl: z
      .string()
      .refine((videoUrl) => {
        if (
          videoUrl.length > 0 &&
          isUrl.test(videoUrl) &&
          (!/youtube(.com|.fr|.co.jp|.co.uk|.be)/.test(videoUrl) ||
            !/watch\?v=[a-zA-Z0-9_]{3,}/.test(videoUrl))
        )
          return false
        return true
      }, "must_be_valid_youtube")
      .optional(),
    message: z
      .string()
      .refine((message) => {
        if (message.length > 0 && isUrl.test(message)) return false
        return true
      }, "no_url_in_message")
      .optional()
  })
  .partial()

Avec ce schéma de validation, nous voulons mettre en place les règles suivantes:

  • Un utilisateur doit au moins saisir une videoUrl ou un message
  • La videoUrl est forcément une vidéo provenant de Youtube
  • Le message ne doit contenir aucune url

Puis, nous ajoutons la vérification sur notre bouton de formulaire:

<button
    type="submit"
    disabled={!formState.isValid}
  >
  <IoIosSend className="mr-2 text-2xl" />
  Send the message
</button>

Maintenant, même si ces règles sont appliqués, les messages d'erreur n'arrivent jamais au formulaire:

// que ce soit
{formState.errors?.message && (
  <span className="text-red-700">
    {translate(
      `formState.errors.message.message`,
      lang
    )}
  </span>
)}

// ou encore
{formState.errors?.videoUrl && (
  <span className="text-red-700">
    {translate(
      `formState.errors.videoUrl.message`,
      lang
    )}
  </span>
)}

Où sont passés mes messages d'erreurs ?

Lorsque l'on essait d'afficher les erreurs sur notre formulaire, React-Hook-Form se comporte très bizarrement. Mais rassurez vous, cela n'a rien à voir avec votre schéma zod ou yup.

zod_form_ko.gif

Même si cela ne fonctionne pas encore, à ce stade vous n'êtes plus très loin !

Afficher les messages d'erreur React-Hook-Form correctement

Reprenons notre définition du formulaire:

import { PostForm, postValidationSchema } from "@/customTypes/post"
import { zodResolver } from "@hookform/resolvers/zod"
import { SubmitHandler, useForm } from "react-hook-form"

const {
    register,
    handleSubmit,
    formState,
    watch,
    trigger
  } = useForm<PostForm>({
    resolver: zodResolver(postValidationSchema)
  })

Cette fois-ci, nous allons ajouter watch et trigger.

Watch permettra de déclencher un changement de state à chaque modification de la valeur des champs. Nous pourrons l'utiliser dans un useEffect.

Trigger quant à lui, sert à forcer un passage au resolver, c'est un vrai douanier !

Utilisons-les comme ceci:

React.useEffect(() => {
    const subscription = watch(() => {
      trigger(["message", "videoUrl"])
    })
    return () => subscription.unsubscribe()
  }, [watch, trigger])

Et maintenant, à quoi ressemble notre formulaire ?

zod_form_ok.gif

J'ai l'impression que tout est ok 😊.

J'espère que cet article vous aidera car je me suis arraché les cheveux pendant 2 jours pour résoudre cette incohérence. Bon code !

#react#react-hook-form#zod#resolver

user picture
Alexandre P.

Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.