Comment valider des objets et tableaux

Comment valider des objets et tableaux

4 min read
Déverrouillez votre potentiel

avec React Router 7

Vous en avez marre de...

❌ perdre du temps à chercher des informations éparpillées
❌ ne pas avoir de retour sur votre progression
Assistant IA spécialisé

Posez vos questions 24/7 à notre IA experte en React Router 7

Quiz interactifs

Validez vos acquis avec des quiz personnalisés et un feedback instantané

9 modules
45 leçons
Accès à vie
299.00
-50%

Démystifier la validation des objets et tableaux

Jusqu’ici tu sais valider des string, number et boolean. Mais la vraie vie, c’est :

  1. un objet imbriqué settings.theme, settings.language ;
  2. un tableau d’objets emails[] avec ses propres règles.

Dans le transcript tu as vu :

  • le navigateur n’envoie que des chaînes → c’est à Zod + Conform d’en déduire les bons types ;
  • une clé HTML peut représenter un chemin : settings[theme] ou emails[0][email].

Voyons comment rendre ça 100 % type-safe et UX-friendly.


Définir une couche modèle réutilisable

app/schemas/user.ts
1
import { z } from 'zod'
2
3
export const userSettingsSchema = z.object({
4
theme: z.enum(['light', 'dark']),
5
language: z.enum(['fr', 'en']),
6
})
7
8
export const userEmailSchema = z.object({
9
email: z.string().email('Email invalide'),
10
active: z.boolean().default(false),
11
})
12
13
/* --- action discriminée : create | update ------------------- */
14
export const userActionSchema = z.discriminatedUnion('action', [
15
z.object({
16
action: z.literal('create-user'),
17
firstName: z.string().min(2),
18
lastName: z.string().min(2),
19
age: z.number(),
20
active: z.boolean(),
21
settings: userSettingsSchema,
22
emails: z.array(userEmailSchema),
23
}),
24
z.object({
25
action: z.literal('update-user'),
26
firstName: z.string().min(2),
27
lastName: z.string().min(2),
28
age: z.number(),
29
active: z.boolean(),
30
settings: userSettingsSchema,
31
emails: 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>.


Côté serveur : parser FormData en un seul appel

app/routes/users.$userSlug.tsx
1
import { parseWithZod } from '@conform-to/zod'
2
import { data, redirect } from 'react-router'
3
import { userActionSchema } from '~/schemas/user'
4
5
export async function action({ request, params }) {
6
const formData = await request.formData()
7
8
const submission = await parseWithZod(formData, {
9
async: true,
10
schema: userActionSchema.superRefine(async (values, ctx) => {
11
if (values.action === 'create-user') {
12
const exists = await getUserBySlug(values.firstName.toLowerCase())
13
if (exists) {
14
ctx.addIssue({
15
path: ['firstName'],
16
code: 'custom',
17
message: 'Ce prénom est déjà pris',
18
})
19
}
20
}
21
}),
22
})
23
24
if (submission.status !== 'success') {
25
// Conform sait lire ce format et replacer les erreurs au bon endroit
26
return data({ result: submission.reply() }, { status: 400 })
27
}
28
29
/* ... 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.

Côté client : fieldset, fieldList et valeurs par défaut

1. Initialiser le hook useForm

app/routes/users.$userSlug.tsx
1
import {
2
useForm,
3
getFormProps,
4
getInputProps,
5
getSelectProps,
6
} from '@conform-to/react'
7
import { userActionSchema } from '~/schemas/user'
8
9
const [form, fields] = useForm({
10
id: 'user-form',
11
lastResult: actionData?.result, // synchro des erreurs serveur
12
constraint: getZodConstraint(userActionSchema), // required, minLength, etc.
13
onValidate({ formData }) {
14
return parseWithZod(formData, { schema: userActionSchema })
15
},
16
defaultValue: {
17
firstName: user?.firstName,
18
lastName: user?.lastName,
19
age: user?.age,
20
active: user?.active,
21
action: isNew ? 'create-user' : 'update-user',
22
settings: user?.settings,
23
emails: user?.emails,
24
},
25
})

2. Manipuler un objet imbriqué : settings

{7,11} app/routes/users.$userSlug.tsx
1
const settingsFieldset = fields.settings.getFieldset()
2
3
<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>
8
9
<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é.
  • L’input HTML final aura un name="settings[theme]".

3. Manipuler un tableau d’objets : emails[]

app/routes/users.$userSlug.tsx
1
const emailFields = fields.emails.getFieldList()
2
3
{emailFields.map((emailField) => {
4
const { email, active } = emailField.getFieldset()
5
return (
6
<div key={emailField.key} className="flex gap-4">
7
<Field
8
labelProps={{ children: 'Email' }}
9
inputProps={getInputProps(email, { type: 'email' })}
10
errors={email.errors}
11
/>
12
<CheckboxField
13
labelProps={{ children: 'Actif' }}
14
buttonProps={getInputProps(active, { type: 'checkbox' })}
15
errors={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.
  • Les valeurs par défaut (issues du loader) gardent le focus et l’état coché.

Comment ça voyage dans le réseau ?

Terminal
1
# Requête POST vue dans Network
2
settings[theme]=dark
3
settings[language]=en
4
emails[0][email]=virgile@algomax.fr
5
emails[0][active]=on
6
emails[1][email]=contact@algomax.fr
7
# emails[1][active] absent → false

Donc :

  • HTML simpleObjet / Tableau reconstitué par Conform + Zod.
  • Le server reçoit un tableau natif, pas un parseur maison.

Résumé des bénéfices

  • Single source of truth : un seul schéma pour client et serveur.
  • Erreurs agrégées : l’utilisateur voit tous les problèmes d’un coup.
  • Focus automatique sur le premier champ invalide (plus de ref bricolée).
  • Typescript first : fields.emails connaît la forme exacte de chaque item.
  • 0 state local supplémentaire : le cycle loader ⇒ action ⇒ loader synchronise tout.
Premium
Quiz interactif
Testez vos connaissances et validez votre compréhension du module avec notre quiz interactif.
1

Comprendre les concepts fondamentaux

Quelle est la principale différence entre les composants client et serveur dans React ?

Les composants client s'exécutent uniquement dans le navigateur
Les composants serveur peuvent utiliser useState
Les composants client sont plus rapides
Il n'y a aucune différence significative
2

Optimisation des performances

Quelle technique est recommandée pour éviter les rendus inutiles dans React ?

Utiliser React.memo pour les composants fonctionnels
Ajouter plus d'états locaux
Éviter d'utiliser les props
Toujours utiliser les class components
3

Architecture des données

Quel hook permet de gérer les effets de bord dans un composant React ?

useEffect
useState
useMemo
useContext
4

Gestion des erreurs

Comment implémenter la gestion des erreurs pour les requêtes API dans React ?

Utiliser try/catch avec async/await
Ignorer les erreurs
Toujours afficher un message d'erreur
Rediriger l'utilisateur
5

Déploiement et CI/CD

Quelle est la meilleure pratique pour déployer une application React en production ?

Utiliser un service CI/CD comme GitHub Actions
Copier les fichiers manuellement via FTP
Envoyer le code source complet
Ne jamais mettre à jour l'application

Débloquez ce quiz et tous les autres contenus premium en achetant ce cours