Valide facilement nombres et booléens dans tes formulaires React avec Zod + Conform : UX fluide, typage strict, valeurs par défaut et zéro string vide côté serveur.
avec React Router 7
Posez vos questions 24/7 à notre IA experte en React Router 7
Validez vos acquis avec des quiz personnalisés et un feedback instantané
Jusqu’ici, on a validé uniquement des chaînes de caractères : e-mail, mot de passe, prénom… Mais les formulaires « réels » contiennent aussi :
Voyons comment intégrer ces deux types en gardant la DX/UX type-safe offerte par Zod et Conform.
Le schéma reste la source de vérité.
On y ajoute age
en number et active
en boolean :
1import { z } from "zod"23export const userCreateSchema = z.object({4action: z.literal("create-user"),5firstName: z.string().min(2, "2 caractères mini"),6lastName: z.string().min(2),7age: z.number({ required_error: "L’âge est requis" }),8active: z.boolean().default(false), // ← coche facultative9})1011export const userUpdateSchema = userCreateSchema.extend({12action: z.literal("update-user"),13})1415export const userActionSchema = z.discriminatedUnion(16"action",17[userCreateSchema, userUpdateSchema],18)
Points clés :
z.number()
assure qu’on reçoit bien un nombre (pas une string vide ou NaN
).default(false)
force active
à false
si la checkbox n’est pas cochée :
on ne stocke plus null
/undefined
.Un <input type="number" value="42" />
ou une <input type="checkbox" checked …>
envoient
toujours des strings dans le FormData
:
1firstName=Alice2age=42 ← "42" (string)3active=on ← "on" (string) ou… rien du tout si décochée
C’est Zod qui transforme "42"
⇒ number
et "on"
⇒ boolean
.
<input type="number">
Rien de spécial côté React : on réutilise notre Field
générique.
1<Field2labelProps={{ children: "Âge" }}3inputProps={{4...getInputProps(fields.age, { type: "number" }),5min: 0,6}}7errors={fields.age.errors}8/>
getInputProps
transmet automatiquement defaultValue={user?.age}
."123"
,
Zod cast en 123
.Une checkbox nécessite un composant un peu plus riche :
aria-invalid
;1import { useInputControl } from "@conform-to/react"2import { useId } from "react"3import { Checkbox } from "./ui/checkbox" // composant Radix stylé4import { ErrorList } from "./ErrorList"56export function CheckboxField({ labelProps, buttonProps, errors }: {...}) {7const id = buttonProps.id ?? useId()8const input = useInputControl({9key: buttonProps.key,10name: buttonProps.name,11formId: buttonProps.form,12initialValue: buttonProps.defaultChecked ? "on" : undefined,13})1415return (16<div className="flex gap-2">17<Checkbox18{...buttonProps}19id={id}20checked={input.value === "on"}21onCheckedChange={(state) => input.change(state.valueOf() ? "on" : "")}22aria-invalid={errors?.length ? true : undefined}23aria-describedby={errors?.length ? `${id}-error` : undefined}24type="button"25/>26<label htmlFor={id} {...labelProps} />27<ErrorList id={`${id}-error`} errors={errors} />28</div>29)30}
Pourquoi ne pas utiliser un simple <input type="checkbox" />
?
Parce qu’on veut une case stylée, contrôlée et accessible sans dupliquer le
boilerplate.
1<CheckboxField2labelProps={{ children: "Actif" }}3buttonProps={{4...getInputProps(fields.active, { type: "checkbox" }),5}}6errors={fields.active.errors}7/>
Si la case est décochée :
active
n’arrive dans le FormData
default(false)
active = false
.Grâce aux types inférés :
1export async function addUser({2firstName, lastName, age, active // ← tout est déjà tipé3}: z.infer<typeof userCreateSchema>) {4const slug = createSlug({ firstName })5/* … */6const newUser: User = {7id: db.users.length + 1,8firstName,9lastName,10slug,11age,12active,13}14db.users.push(newUser)15return { slug }16}
Aucun changement manuel : dès qu’on ajoute un champ dans le schéma, la signature de la fonction se met à jour et TypeScript protège le serveur.
undefined
ou de string vide : la donnée arrive prête à persister.FormData
.
C’est Zod qui caste vers number
ou boolean
.z.number()
+ z.boolean().default(false)
couvrent 90 % des cas métier.useInputControl
facilite la conversion "on"
⇔ boolean
.Quelle est la principale différence entre les composants client et serveur dans React ?
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Quel hook permet de gérer les effets de bord dans un composant React ?
Comment implémenter la gestion des erreurs pour les requêtes API dans React ?
Quelle est la meilleure pratique pour déployer une application React en production ?