Comment valider des objets et tableaux
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 tu sais valider des string
, number
et boolean
.
Mais la vraie vie, c’est :
settings.theme
, settings.language
;emails[]
avec ses propres règles.Dans le transcript tu as vu :
settings[theme]
ou emails[0][email]
.Voyons comment rendre ça 100 % type-safe et UX-friendly.
1import { z } from 'zod'23export const userSettingsSchema = z.object({4theme: z.enum(['light', 'dark']),5language: z.enum(['fr', 'en']),6})78export const userEmailSchema = z.object({9email: z.string().email('Email invalide'),10active: z.boolean().default(false),11})1213/* --- action discriminée : create | update ------------------- */14export const userActionSchema = z.discriminatedUnion('action', [15z.object({16action: z.literal('create-user'),17firstName: z.string().min(2),18lastName: z.string().min(2),19age: z.number(),20active: z.boolean(),21settings: userSettingsSchema,22emails: z.array(userEmailSchema),23}),24z.object({25action: z.literal('update-user'),26firstName: z.string().min(2),27lastName: z.string().min(2),28age: z.number(),29active: z.boolean(),30settings: userSettingsSchema,31emails: z.array(userEmailSchema),32}),33])
Pourquoi trois petits schémas ?
– Tu peux importer userSettingsSchema
ou userEmailSchema
dans d’autres formulaires sans toucher au reste.
– Chaque schéma devient un type TypeScript fiable :
z.infer<typeof userEmailSchema>
.
1import { parseWithZod } from '@conform-to/zod'2import { data, redirect } from 'react-router'3import { userActionSchema } from '~/schemas/user'45export async function action({ request, params }) {6const formData = await request.formData()78const submission = await parseWithZod(formData, {9async: true,10schema: userActionSchema.superRefine(async (values, ctx) => {11if (values.action === 'create-user') {12const exists = await getUserBySlug(values.firstName.toLowerCase())13if (exists) {14ctx.addIssue({15path: ['firstName'],16code: 'custom',17message: 'Ce prénom est déjà pris',18})19}20}21}),22})2324if (submission.status !== 'success') {25// Conform sait lire ce format et replacer les erreurs au bon endroit26return data({ result: submission.reply() }, { status: 400 })27}2829/* ... INSERT/UPDATE BDD puis redirect ... */30}
Points clés du transcript reformulés :
superRefine
te laisse déclencher des vérifs async (unicité, etc.)submission.reply()
encapsule toutes les erreurs → plus de JSON custom maison.useForm
1import {2useForm,3getFormProps,4getInputProps,5getSelectProps,6} from '@conform-to/react'7import { userActionSchema } from '~/schemas/user'89const [form, fields] = useForm({10id: 'user-form',11lastResult: actionData?.result, // synchro des erreurs serveur12constraint: getZodConstraint(userActionSchema), // required, minLength, etc.13onValidate({ formData }) {14return parseWithZod(formData, { schema: userActionSchema })15},16defaultValue: {17firstName: user?.firstName,18lastName: user?.lastName,19age: user?.age,20active: user?.active,21action: isNew ? 'create-user' : 'update-user',22settings: user?.settings,23emails: user?.emails,24},25})
settings
1const settingsFieldset = fields.settings.getFieldset()23<label htmlFor={settingsFieldset.theme.id}>Thème</label>4<select {...getSelectProps(settingsFieldset.theme)}>5<option value="light">Light</option>6<option value="dark">Dark</option>7</select>89<label htmlFor={settingsFieldset.language.id}>Langue</label>10<select {...getSelectProps(settingsFieldset.language)}>11<option value="fr">Français</option>12<option value="en">Anglais</option>13</select>
getFieldset()
te fournit les props Auto-ID + erreurs de chaque propriété.name="settings[theme]"
.emails[]
1const emailFields = fields.emails.getFieldList()23{emailFields.map((emailField) => {4const { email, active } = emailField.getFieldset()5return (6<div key={emailField.key} className="flex gap-4">7<Field8labelProps={{ children: 'Email' }}9inputProps={getInputProps(email, { type: 'email' })}10errors={email.errors}11/>12<CheckboxField13labelProps={{ children: 'Actif' }}14buttonProps={getInputProps(active, { type: 'checkbox' })}15errors={active.errors}16/>17</div>18)19})}
Traduction :
getFieldList()
retourne chaque élément du tableau avec sa clé stable.emails[0][email]
, emails[0][active]
… sont générés automatiquement.1# Requête POST vue dans Network2settings[theme]=dark3settings[language]=en4emails[0][email]=virgile@algomax.fr5emails[0][active]=on6emails[1][email]=contact@algomax.fr7# emails[1][active] absent → false
Donc :
ref
bricolée).fields.emails
connaît la forme exacte de chaque item.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 ?