Comment soumettre plusieurs formulaires en parallèle

Intègre plusieurs formulaires indépendants sur une même page avec useFetcher() pour éviter les scrolls, gérer les états localement et offrir une UX fluide et réactive.

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%

Pourquoi intégrer plusieurs formulaires dans une même page ?

Sur une seule et même page, tu peux vouloir : - éditer un utilisateur

  • supprimer un utilisateur
  • créer un compte

Le tout, sans changer d'onglet ni remonter en haut du viewport à chaque clic. Le composant <Form> de React Router 7 gère très bien un formulaire unique, mais dès que tu enchaînes les actions simultanées tu te heurtes à deux limites :

  • le navigateur recalcule le scroll après chaque POST
  • useNavigation() expose l’état global : un seul submitting pour toute la page.

C’est là qu’entre en scène le hook useFetcher() : il te donne un mini-client HTTP local à chaque formulaire.


Mettre en place un fetcher par ligne de tableau

1. Crée ton action serveur

app/routes/users.$userSlug.tsx
1
export async function action({ request, params }: ActionFunctionArgs) {
2
const fd = await request.formData()
3
const slug = params.userSlug
4
5
if (fd.get('action') === 'delete-user') {
6
await deleteUser({ slug }) // 🚮 suppression BDD
7
return redirect('/users') // on reste sur /users
8
}
9
10
/* … autres mutations … */
11
}

2. Instancie le fetcher côté client

app/components/UserItem.tsx
1
import { useFetcher, href } from "react-router"
2
3
export function UserItem({ user }: { user: User }) {
4
const fetcher = useFetcher<typeof action>()
5
6
const isSubmitting = fetcher.state === "submitting"
7
8
return (
9
<li className={`flex gap-2 ${isSubmitting ? "opacity-50" : ""}`}>
10
{/* lien classique vers la page de détail */}
11
<a className="flex-1" href={href('/users/:userSlug',{userSlug:user.slug})}>
12
{user.firstName} {user.lastName}
13
</a>
14
15
{/* le (mini) formulaire isolé */}
16
<fetcher.Form
17
method="POST"
18
action={href('/users/:userSlug',{userSlug:user.slug})}
19
className="flex gap-2"
20
>
21
<input type="hidden" name="action" value="delete-user" />
22
<button type="submit" disabled={isSubmitting}
23
className="bg-red-500 text-white px-2 py-1 rounded">
24
Supprimer
25
</button>
26
</fetcher.Form>
27
28
{/* affiche les éventuelles erreurs */}
29
{fetcher.data?.result?.error && (
30
<p className="text-xs text-red-600">{fetcher.data.result.error}</p>
31
)}
32
</li>
33
)
34
}

Points importants :

  • chaque appel à useFetcher() retourne son propre state → aucun risque que le bouton de l’utilisateur A devienne gris pendant que B est en cours de suppression ;
  • on évite le scroll automatique grâce à
    1
    <fetcher.Form preventScrollReset />
    (non-nécessaire dans l’exemple, mais bon réflexe).

Optimistic UI : masquer la ligne instantanément

La promesse d’un fetcher étant locale, tu peux donner l’illusion d’une suppression immédiate :

app/components/UserItem.tsx {3,11-13}
1
const isDeleting = fetcher.state === "submitting"
2
if (isDeleting) return null // 🪄 effacé côté UI

Si le serveur renvoie finalement une erreur, React Router rejouera le loader parent, et la ligne réapparaîtra avec le message d’échec — parfait pour conserver la source de vérité côté back-end.


Afficher un toast d’erreur depuis fetcher.data

Le serveur t’envoie un JSON :

1
{ "result": { "error": "L'utilisateur n'existe pas." } }

Coté client :

app/components/UserItem.tsx {22-25}
1
if (fetcher.state === "idle" && fetcher.data?.result?.error) {
2
toast.error(fetcher.data.result.error)
3
}

Pas besoin de remonter l’information dans un store global : le fetcher porte sa propre réponse.


Comparaison navigation globale VS fetcher local

useNavigation()useFetcher()
StateUnique pour toute la page1 par formulaire
Scrollremonte en haut (sauf preventScrollReset)Aucun changement
Redirectionsuit le header Locationidem
Optimistic UIcomplexe (doit filtrer par form)trivial (state==="submitting")

Pattern avancé : déclencher sans balise <form>

Tu peux déclencher un POST depuis n’importe quel handler :

app/routes/users._usersLayout.tsx
1
const fetcher = useFetcher()
2
3
function handleBulkDelete(ids: string[]) {
4
fetcher.submit(
5
{ action: "bulk-delete", ids },
6
{ method: "POST", action: "/users" }
7
)
8
}

Pratique pour un bouton « Supprimer la sélection » qui ne s’affiche qu’au survol de la table.


Points clés à retenir

  • useFetcher() fournit un mini-formulaire isolé, idéal pour les listes.
  • Le state est local : plusieurs mutations tournent en parallèle sans se bloquer.
  • Ajoute preventScrollReset si tu veux maintenir la position de l’utilisateur.
  • fetcher.data reflète la dernière réponse HTTP (succès ou erreur) — parfait pour un toast ou un message inline.
  • Combine-le avec Zod + Conform pour valider et typer les erreurs (cf. leçon « Validation serveur »).
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