Apprends à ajouter et supprimer des éléments dans un tableau avec Conform : pas de state local, erreurs précises, gestion des doublons et formulaire entièrement type-safe.
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é
Nous avons vu comment validé des objets et des tableaux avec Zod et Conform. Sauf qu'il nous manque une information : Comment rajouter ou retirer des éléments à notre tableau d'objets ?
Dans cette leçon tu vas :
form.insert()
et form.remove()
pour manipuler le tableau côté client, sans state local.Notre schéma Zod inclut déjà un champ tableau :
1export const userEmailSchema = z.object({2email: z.string().email('Email invalide'),3active: z.boolean().default(false),4})5// ...6emails: z.array(userEmailSchema),
Conform est déjà configuré pour valider la donnée avec Zod. Nous devons maintenant ajouter la fonctionnalité côté client.
form.insert
La façon la plus simple : un <button type="button">
qui appelle form.insert()
.
1<button2type="button"3className="px-4 py-2 bg-emerald-600 text-white rounded-md"4onClick={() =>5form.insert({6name: fields.emails.name, // 🗝️ chemin vers le tableau7defaultValue: { email: '', active: true },8})9}10>11Ajouter un e-mail12</button>
form.insert()
reçoit :
name
– le chemin du tableau (emails
);defaultValue
– l’objet pré-rempli pour la nouvelle case.Résultat : un nouvel input apparaît instantanément, aucune page rechargée.
Conform expose la même fonctionnalité via props pré-générées, pratiques pour l’accessibilité :
1<button2{...form.insert.getButtonProps({3name: fields.emails.name,4defaultValue: { email: '', active: true },5})}6className="px-4 py-2 bg-emerald-600 text-white rounded-md"7>8Ajouter un e-mail9</button>
Le bouton devient type="submit"
avec un name
+ value
spéciaux ; Conform intercepte la soumission et réalise l’insertion avant d’envoyer le vrai formulaire.
À toi de choisir la technique qui colle le mieux à ta stack ; dans la vidéo on garde la version onClick
.
Conform fournit form.remove()
; il attend l’index
à retirer.
On connaît cet index grâce à map()
:
1{emailsFieldList.map((emailField, index) => (2<div key={emailField.key} className="flex gap-2">3{/* …input email + checkbox… */}4<button5type="button"6className="px-4 py-2 bg-red-600 text-white rounded-md"7onClick={() =>8form.remove({9name: fields.emails.name,10index, // 🗑️ position à retirer11})12}13>14Supprimer15</button>16</div>17))}
• L’item disparaît en DOM ; • La pagination des index est mise à jour (important pour les validations serveur).
Quand tu fais :
1emails[0][email]=a@ex.com2emails[1][email]=a@ex.com
le serveur reçoit deux fois la même adresse à deux positions différentes. Pour cibler le champ précis, on passe le chemin complet à Zod :
1if (hasDuplicateEmails) {2duplicateIndexes.forEach((i) =>3ctx.addIssue({4path: ['emails', i, 'email'], // ← index dynamique5code: 'custom',6message: 'Cet e-mail est déjà présent',7}),8)9}
Conform affiche alors le même message sous chaque input fautif et place le focus sur le premier. Zéro troll pour l’utilisateur, zéro migraine pour toi !
Méthode | Rôle |
---|---|
form.insert({...}) | Ajoute un item (tableau) ou un champ (objet). |
form.remove({...}) | Supprime l'item à l'index donné. |
form.reorder({...}) | Change l'ordre (drag & drop, flèches…). |
form.update({...}) | Écrase la valeur d’un champ existant. |
form.reset() | Réinitialise le formulaire aux valeurs par défaut. |
P.S: Tu n'utiliseras surement que insert
et remove
.Mais Conform en expose d'autres 😉.
1// extrait condensé2<button3type="button"4onClick={() =>5form.insert({6name: fields.emails.name,7defaultValue: { email: '', active: true },8})9}10/>11{/* …map sur emails… */}12<button13type="button"14onClick={() =>15form.remove({ name: fields.emails.name, index })16}17/>
1// action – détection des doublons2if (data.emails.length > 0) {3const seen = new Map<string, number[]>()4data.emails.forEach((e, i) =>5seen.set(e.email, [...(seen.get(e.email) ?? []), i]),6)7for (const [email, idx] of seen.entries()) {8if (idx.length > 1) {9idx.forEach((i) =>10ctx.addIssue({11path: ['emails', i, 'email'],12code: 'custom',13message: 'Cet e-mail est déjà présent',14}),15)16}17}18}
👍 À retenir : l’index vit de la soumission client au parseur Zod, donc c’est fiable même si l’utilisateur ajoute, supprime puis re-ajoute avant de cliquer sur Enregistrer.
form.insert
/form.remove
offrent une UX réactive sans useState
.getButtonProps
) te donnent la même puissance en pur HTML.[tableau, index, champ]
.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 ?