/ Articles
Créer des formulaires modernes et type-safe avec TanStack Form 📝 [Bonus]
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)
},
})