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.

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%

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 :

  1. Utiliser form.insert() et form.remove() pour manipuler le tableau côté client, sans state local.
  2. Assigner une intention à ton bouton via `getButtonProps.
  3. Propager l’index jusqu’au serveur pour remonter des erreurs précises.
  4. Détecter et signaler les doublons avant de sauvegarder.

Notre schéma Zod inclut déjà un champ tableau :

app/schemas/user.ts
1
export const userEmailSchema = z.object({
2
email: z.string().email('Email invalide'),
3
active: z.boolean().default(false),
4
})
5
// ...
6
emails: 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().

app/routes/users.$userSlug.tsx
1
<button
2
type="button"
3
className="px-4 py-2 bg-emerald-600 text-white rounded-md"
4
onClick={() =>
5
form.insert({
6
name: fields.emails.name, // 🗝️ chemin vers le tableau
7
defaultValue: { email: '', active: true },
8
})
9
}
10
>
11
Ajouter un e-mail
12
</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é :

app/routes/users.$userSlug.tsx
1
<button
2
{...form.insert.getButtonProps({
3
name: fields.emails.name,
4
defaultValue: { email: '', active: true },
5
})}
6
className="px-4 py-2 bg-emerald-600 text-white rounded-md"
7
>
8
Ajouter un e-mail
9
</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() :

app/routes/users.$userSlug.tsx
1
{emailsFieldList.map((emailField, index) => (
2
<div key={emailField.key} className="flex gap-2">
3
{/* …input email + checkbox… */}
4
<button
5
type="button"
6
className="px-4 py-2 bg-red-600 text-white rounded-md"
7
onClick={() =>
8
form.remove({
9
name: fields.emails.name,
10
index, // 🗑️ position à retirer
11
})
12
}
13
>
14
Supprimer
15
</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 :

network payload
1
emails[0][email]=a@ex.com
2
emails[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 :

app/routes/users.$userSlug.tsx
1
if (hasDuplicateEmails) {
2
duplicateIndexes.forEach((i) =>
3
ctx.addIssue({
4
path: ['emails', i, 'email'], // ← index dynamique
5
code: 'custom',
6
message: '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éthodeRô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

app/routes/users.$userSlug.tsx
1
// extrait condensé
2
<button
3
type="button"
4
onClick={() =>
5
form.insert({
6
name: fields.emails.name,
7
defaultValue: { email: '', active: true },
8
})
9
}
10
/>
11
{/* …map sur emails… */}
12
<button
13
type="button"
14
onClick={() =>
15
form.remove({ name: fields.emails.name, index })
16
}
17
/>
app/routes/users.$userSlug.tsx {100-133}
1
// action – détection des doublons
2
if (data.emails.length > 0) {
3
const seen = new Map<string, number[]>()
4
data.emails.forEach((e, i) =>
5
seen.set(e.email, [...(seen.get(e.email) ?? []), i]),
6
)
7
for (const [email, idx] of seen.entries()) {
8
if (idx.length > 1) {
9
idx.forEach((i) =>
10
ctx.addIssue({
11
path: ['emails', i, 'email'],
12
code: 'custom',
13
message: '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.remove offrent une UX réactive sans useState.
  • 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.
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