by

/ Articles

Créer des formulaires modernes et type-safe avec TanStack Form 📝 [Bonus]

August 16, 2025

Après la mise en place d'un formulaire TanStack Form (partie 1) puis l'ajout de validations et l'affichage d'erreurs (partie 2), voici quelques idées d'amélioration faciles dont peuvent bénéficier la plupart des formulaires et faciles à mettre en œuvre avec TanStack Form.

Tip #1 - Ajouter un délai avant validation (debounce)

Lorsqu'on souhaite valider un input à la fin d'une saisie, introduire un léger délai avant vérification évite d'enclencher une validation prématurée inutile. Cette pause pour s'assurer que l'input est "stabilisé" avant de le tester permet d'optimiser notre formulaire surtout si la validation requiert un appel API.

Si on combine OnChangeAsyncDebounceMs avec onChangeAsync on peut ainsi trouver un rythme de validation plus intelligent 👇


  function validateUsername(value: string) {
    fetch(`/api/validate-username?username=${value}`)
      .then(response => response.json())
      .then(data => {
        if (data.error) {
          return "Ce nom d'utilisateur est déjà pris"
        }
        return null
      })
      .catch(error => {
        return "Une erreur est survenue lors de la validation du nom d'utilisateur"
      })
  }
 
  <form.Field 
        name="username" 
        validators={
          // On prend ici pour l'exemple une validation asynchrone
          // mais on peut aussi introduire un debounce sur une validation synchrone
          onChangeAsyncDebounceMs: 500,
          onChangeAsync: ({ value }) => validateUsername(value),
          onChange: ({ value }) => {
            if (value.length < 3) {
              return "Le nom d'utilisateur doit contenir au moins 3 caractères"
            }
          }
        }
        children={({ field }) => 
          <input 
            id={field.id} 
            type="text"
            name={field.name} 
            value={field.state.value}
            onChange={(e) => field.handleChange(e.target.value)}
          />
        }
      />

Tip #2 - Ajouter un indicateur de validation en cours

Quelle que soit la durée de la validation, n'importe quel champ peut afficher son propre indicateur de validation en regardant son field.getMeta().isValidating.

  <form.Field 
        name="username" 
        children={({ field }) => 
          <>
            <input 
              id={field.id} 
              type="text"
              name={field.name} 
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
            />
            {field.getMeta().isValidating && <div>Validation en cours...</div>}
          </>
        }
      />

Tip #3 - Valider plusieurs champs en mĂŞme temps

Pensons à l'exemple courant d'un formulaire de création de compte comportant un champ pour entrer un mot de passe et un autre pour le confirmer en le rentrant à l'identique. Chacun de ces champs n'est valide que si l'autre contient la même valeur.

En précisant à onChangeListenTo ou onBlurListenTo le tableau de champs qu'on souhaite lui lier, on s'assure que les deux champs sont toujours re-validés en même temps même si l'utilisateur n'interagit qu'avec l'un des deux.

  <form.Field 
        name="password" 
        validators={{
          onChangeListenTo: ["confirmPassword"],
          onChange: ({ value, fieldApi }) => {
            if (value !== fieldApi.form.getFieldValue("confirmPassword")) {
              return "Les mot de passe ne correspondent pas"
            }
          }
        }}
        children={({ field }) => 
            <input 
              id={field.id} 
              type="password"
              name={field.name} 
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
            />
        }
      />
  <form.Field 
        name="confirmPassword" 
        validators={{
          onChangeListenTo: ["password"],
          onChange: ({ value, fieldApi }) => {
            if (value !== fieldApi.form.getFieldValue("password")) {
              return "Les mot de passe ne correspondent pas"
            }
          }
        }}
        children={({ field }) => 
            <input 
              id={field.id} 
              type="password"
              name={field.name} 
              value={field.state.value}
              onChange={(e) => field.handleChange(e.target.value)}
            />
        }
      />

On remarquera au passage que nous pouvons récupérer au niveau du field n'importe quelle valeur du form grâce à fieldApi.form.getFieldValue() 🤓

Tip #4 - Utiliser une librairie de validation externe

Plutôt que de définir notre logique de validation à l'aide de fonctions passées dans les validators du formulaire ou du champ, TanStack Form laisse la possibilité d'utiliser des schémas de validation de Zod, Valibot, ArkType et autres librairies pour définir nos règles.

L'association de TanStack Form avec une librairie de validation requiert parfois d'installer un adaptateur pour faire le pont entre les deux. Dans le cas de Zod, on l'obtient avec npm install @tanstack/zod-form-adapter

On passe ensuite notre schéma de validation à onChange, onBlurou onSubmit au niveau du champ... 👇

import { zodValidator } from "@tanstack/zod-form-adapter"
import { z } from "zod"
import { useForm } from "@tanstack/react-form"
 
const PasswordSchema = z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères")
 
  <form.Field 
        name="password" 
        validators={{
          onChange: ({ value, fieldApi }) => {
            if (value !== fieldApi.form.getFieldValue("confirmPassword")) {
              return "Les mot de passe ne correspondent pas"
            }
          }
        }}
        children={({ field })> 
            <input 
              id={field.id} 
              type="password"
              name={field.name} 
              value={field.state.value}
              validatorAdapter={zodValidator}
              onChange={PasswordSchema}
            />
        }
      />

...ou du formulaire tout entier 👇

import { useForm } from "@tanstack/react-form"
import { zodValidator } from "@tanstack/zod-form-adapter"
import { z } from "zod"
 
const LoginSchema = z.object({
  username: z.string().min(3, "Le nom de l'utilisateur doit contenir au moins 3 caractères"),
  password: z.string().min(8, "Le mot de passe doit contenir au moins 8 caractères")
})
 
const form = useForm({ 
  defaultValues: {
    username: "",
    password: "",
  },
  validators: {
    onChange: LoginSchema,
  },
  validatorAdapter: zodValidator(),
  onSubmit: async ({ value}) => {
    console.log(value)
  },
 })