Chargement…

Comment ajouter des validateurs custom sur react-hook-form

Comment ajouter des validateurs custom sur react-hook-form

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 !

FAQ

Pourquoi formState.errors reste-t-il toujours vide malgré un resolver zod correctement configuré ?

React-Hook-Form ne déclenche pas automatiquement la validation à chaque frappe lorsqu'un resolver est utilisé. Sans forcer le passage par le resolver, les erreurs ne remontent jamais dans formState.

À quoi servent watch et trigger dans ce contexte ?

watch permet de détecter chaque modification des champs du formulaire, tandis que trigger force la réévaluation du resolver sur les champs concernés. Utilisés ensemble dans un useEffect, ils synchronisent formState avec l'état réel des champs.

Est-ce que ce problème est spécifique à zod ou concerne aussi yup ?

Ce comportement n'est pas lié au schéma de validation utilisé. Le même problème peut survenir avec yup ou tout autre resolver, et la solution avec watch et trigger s'applique de la même façon.

Peut-on cibler uniquement certains champs avec trigger plutôt que tout le formulaire ?

Oui, trigger accepte un nom de champ ou un tableau de noms, comme trigger(["message", "videoUrl"]). Cela permet de limiter la validation aux champs pertinents sans réévaluer l'ensemble du formulaire.


Poursuivre la lecture

Organiser efficacement ses requêtes React-Query Dev
#react#react-query#hook

Organiser efficacement ses requêtes React-Query

Tous les projets React ne sont pas égaux en matière d'organisation des requêtes. J'aime bien que les dossiers ne soient pas un labyrinthe et que l'on sache exactement quoi faire pour répliquer l'existant sans créer de doublons. Voici comment je procède.

Alexandre P.
Comment formater son texte sur des composants React Dev
#react#text#library

Comment formater son texte sur des composants React

Apprenez à formater proprement vos templates string en React et améliorer la lisibilité de vos composants frontend.

Alexandre P.