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.
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é
Pourquoi intégrer plusieurs formulaires dans une même page ?
Sur une seule et même page, tu peux vouloir : - éditer un utilisateur
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 :
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.
1export async function action({ request, params }: ActionFunctionArgs) {2const fd = await request.formData()3const slug = params.userSlug45if (fd.get('action') === 'delete-user') {6await deleteUser({ slug }) // 🚮 suppression BDD7return redirect('/users') // on reste sur /users8}910/* … autres mutations … */11}
1import { useFetcher, href } from "react-router"23export function UserItem({ user }: { user: User }) {4const fetcher = useFetcher<typeof action>()56const isSubmitting = fetcher.state === "submitting"78return (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>1415{/* le (mini) formulaire isolé */}16<fetcher.Form17method="POST"18action={href('/users/:userSlug',{userSlug:user.slug})}19className="flex gap-2"20>21<input type="hidden" name="action" value="delete-user" />22<button type="submit" disabled={isSubmitting}23className="bg-red-500 text-white px-2 py-1 rounded">24Supprimer25</button>26</fetcher.Form>2728{/* 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 :
useFetcher()
retourne son propre state
→ aucun risque que le bouton de l’utilisateur A devienne gris pendant que
B est en cours de suppression ;1<fetcher.Form preventScrollReset />
La promesse d’un fetcher étant locale, tu peux donner l’illusion d’une suppression immédiate :
1const isDeleting = fetcher.state === "submitting"2if (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.
Le serveur t’envoie un JSON :
1{ "result": { "error": "L'utilisateur n'existe pas." } }
Coté client :
1if (fetcher.state === "idle" && fetcher.data?.result?.error) {2toast.error(fetcher.data.result.error)3}
Pas besoin de remonter l’information dans un store global : le fetcher porte sa propre réponse.
useNavigation() | useFetcher() | |
---|---|---|
State | Unique pour toute la page | 1 par formulaire |
Scroll | remonte en haut (sauf preventScrollReset ) | Aucun changement |
Redirection | suit le header Location | idem |
Optimistic UI | complexe (doit filtrer par form) | trivial (state==="submitting" ) |
<form>
Tu peux déclencher un POST depuis n’importe quel handler :
1const fetcher = useFetcher()23function handleBulkDelete(ids: string[]) {4fetcher.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.
useFetcher()
fournit un mini-formulaire isolé, idéal pour les listes.state
est local : plusieurs mutations tournent en parallèle sans se bloquer.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.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 ?