Comment valider des nombres et booléens
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.
Valider des nombres et booléens avec Zod + Conform
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 :
- des nombres (âge, quantité, prix, pourcentage) ;
- des booléens (case à cocher « actif », « j’accepte les CGV », etc.).
Voyons comment intégrer ces deux types en gardant la DX/UX type-safe offerte par Zod et Conform.
1. Étendre le schéma Zod
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 ouNaN).default(false)forceactiveàfalsesi la checkbox n’est pas cochée : on ne stocke plusnull/undefined.
2. Rappels HTML : tout arrive comme string
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.
3. Champ <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/>
getInputPropstransmet automatiquementdefaultValue={user?.age}.- Si l’utilisateur tape « abc », Conform bloque l’envoi ; si l’on reçoit
"123", Zod cast en123.
4. Champ checkbox réutilisable
Une checkbox nécessite un composant un peu plus riche :
- convertir « on / undefined » en booléen ;
- gérer
aria-invalid; - afficher les erreurs.
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.
5. Brancher la checkbox dans le formulaire
1<CheckboxField2labelProps={{ children: "Actif" }}3buttonProps={{4...getInputProps(fields.active, { type: "checkbox" }),5}}6errors={fields.active.errors}7/>
Si la case est décochée :
- aucune key
activen’arrive dans leFormData - Zod applique le
default(false) - la base de données reçoit
active = false.
6. Mettre à jour le backend… sans douleur
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.
7. Résultat : UX & DX solides
- Validation client immédiate (Conform) Saisie « -1 » ⇒ message rouge sous le champ
- Validation serveur (Zod + superRefine) Slug déjà pris ⇒ erreur renvoyée, focus sur le prénom
- Pas de
undefinedou de string vide : la donnée arrive prête à persister.
Points clés
- Tout arrive en string dans
FormData. C’est Zod qui caste versnumberouboolean. z.number()+z.boolean().default(false)couvrent 90 % des cas métier.- Une checkbox décochée n’envoie rien : définis une valeur par défaut.
useInputControlfacilite la conversion"on"⇔boolean.- Grâce aux types générés, le serveur n’a aucun patch manuel à prévoir.
Comprendre les concepts fondamentaux
Quelle est la principale différence entre les composants client et serveur dans React ?
Optimisation des performances
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Architecture des données
Quel hook permet de gérer les effets de bord dans un composant React ?