Comment ajouter et retirer des éléments d'un tableau
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.
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 :
- Utiliser
form.insert()etform.remove()pour manipuler le tableau côté client, sans state local. - Assigner une intention à ton bouton via `getButtonProps.
- Propager l’index jusqu’au serveur pour remonter des erreurs précises.
- Détecter et signaler les doublons avant de sauvegarder.
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.
Ajouter un élément au tableau
Ajouter un bouton avec 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.
Solution alternative : ajouter une intention à notre bouton (HTML pur)
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.
Supprimer un élément du tableau
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).
Focus : les chemins d’erreurs indexés
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 !
Récap des méthodes Conform
| 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 😉.
Mise en pratique complète
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.
Points clés
form.insert/form.removeoffrent une UX réactive sansuseState.- Les props intentionnelles (
getButtonProps) te donnent la même puissance en pur HTML. - Pour renvoyer une erreur sur un item précis, indique le chemin
[tableau, index, champ]. - SuperRefine peut boucler sur les données et générer plusieurs erreurs d’un coup.
- L’utilisateur garde le focus et la saisie ; toi tu restes 100 % type-safe.
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 ?