Comment soumettre un formulaire dans une fonction

Apprends à déclencher un formulaire manuellement avec useFetcher et useSubmit : plusieurs actions par page, champs dynamiques, validation serveur et typage précis.

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
57 leçons
Accès à vie
299.49
-35%

Pourquoi sortir du <Form> classique ?

Le composant <Form> de React Router 7 fait un travail remarquable : il sérialise ton FormData, déclenche l’action, relance le loader et met à jour l’UI. Dans 90 % des cas, c’est the way to go.

Mais il existe des situations où tu vas préférer appeler la soumission toi-même :

  1. Tu as plusieurs actions indépendantes dans la même page (supprimer un élément, liker, archiver…) et tu veux un indicateur d’envoi pour chacune.
  2. Tu dois injecter une valeur dynamique au dernier moment (ex. jeton reCAPTCHA, timestamp, signature HMAC).
  3. Tu souhaites factoriser la logique dans une fonction réutilisable au lieu de répéter un <Form> à chaque ligne d’un tableau.
  4. Tu veux bénéficier d’un typage TypeScript strict sur le payload envoyé à l’action.

Pour ces cas-là, place au duo useFetcher() et useSubmit() !


Utiliser fetcher.submit pour une action locale

useFetcher() renvoie un objet avec la même API que <Form>, mais il n’affecte pas la navigation principale. Idéal pour un bouton « Supprimer ».

app/components/UserItem.tsx
1
import { useFetcher } from "react-router"
2
import { z } from "zod"
3
import { UserDeleteSchema } from "~/routes/users+/$userSlug"
4
5
export const UserItem = ({ user }) => {
6
const fetcher = useFetcher() // ↙ hook local
7
const isSubmitting = fetcher.state === "submitting"
8
9
const handleDelete = () => {
10
const formData: z.infer<typeof UserDeleteSchema> = {
11
action: "delete-user",
12
slug: user.slug,
13
}
14
fetcher.submit(formData, {
15
method: "POST",
16
action: `/users/${user.slug}`,
17
})
18
}
19
20
return (
21
<li className="flex justify-between items-center">
22
{/* …nom, avatars, etc.… */}
23
<button
24
onClick={handleDelete}
25
disabled={isSubmitting}
26
className="bg-red-600 text-white px-2 py-1 rounded"
27
>
28
{isSubmitting ? "…" : "Supprimer"}
29
</button>
30
</li>
31
)
32
}

Ce qu’il faut retenir

  • fetcher.submit accepte un objet littéral – Remix le transforme en FormData pour toi.
  • Tu précises la method et le action cible ; aucune navigation n’est déclenchée.
  • fetcher.state vaut submitting → parfait pour afficher un loader.

useSubmit : même puissance, navigation incluse

Parfois tu veux quand même naviguer (redirect, page de confirmation…) mais sans écrire de <form>. useSubmit() est ta boîte à outils :

app/routes/users+/$userSlug.tsx
1
export default function UserButtons({ user }) {
2
const submit = useSubmit()
3
4
const deleteUser = () => {
5
submit(
6
{ action: "delete-user", slug: user.slug }, // payload
7
{ method: "POST", action: `/users/${user.slug}` }
8
)
9
}
10
11
return (
12
<button onClick={deleteUser} className="btn-danger">
13
Supprimer
14
</button>
15
)
16
}

Ici la navigation principale passe en submitting. Côté UX c’est équivalent à un <Form> classique, mais tu as le contrôle programmatique sur le contenu envoyé.


Ajouter un champ « fantôme » avant l’envoi

Prenons un cas réel : sur algomax.fr j’utilise Google reCAPTCHA v2 pour protéger le formulaire de contact.

Le token n’est connu qu’après le clic utilisateur ; impossible de le mettre dans un <input> statique. Solution : construire le FormData à la volée !

app/routes/contact.tsx
1
import { useFetcher } from "react-router"
2
3
export default function ContactForm() {
4
const fetcher = useFetcher()
5
6
const handleSubmit = async (e: React.FormEvent) => {
7
e.preventDefault()
8
9
// 1. Récupère les champs saisis
10
const formData = new FormData(e.currentTarget)
11
// 2. Appelle l’API reCAPTCHA
12
const token = await grecaptcha.execute(
13
import.meta.env.PUBLIC_RECAPTCHA_KEY,
14
{ action: "contact" }
15
)
16
// 3. Injecte le token
17
formData.set("captcha", token)
18
// 4. Envoie via fetcher
19
fetcher.submit(formData, { method: "POST", action: "/contact" })
20
}
21
22
return (
23
<form onSubmit={handleSubmit} className="space-y-4">
24
{/* inputs name + email… */}
25
<button className="btn-primary">Envoyer</button>
26
</form>
27
)
28
}

Avantage : aucun champ « captcha » n’apparaît dans ton HTML, et tu gardes la validation serveur dans l’action.


Super-pouvoirs TypeScript : typer le payload

Quand tu passes un objet littéral à submit, TypeScript peut vérifier que tu n’oublies rien :

utils/types.ts
1
import { z } from "zod"
2
import { UserDeleteSchema } from "~/routes/users+/$userSlug"
3
4
export type DeleteUserPayload = z.infer<typeof UserDeleteSchema>
app/components/UserItem.tsx
1
const formData: DeleteUserPayload = {
2
action: "delete-user",
3
slug: user.slug, // 🛑 si tu oublies la clé => erreur compile !
4
}

➜ Moins de fautes de frappe, et ton backend reçoit toujours l’objet attendu.


Résumé des points clés

  • <Form> : parfait pour un seul submit, DX ultra-simple.
  • useFetcher() : plusieurs mini-actions indépendantes sans navigation ; chaque fetcher expose son propre state.
  • useSubmit() : navigation classique mais déclenchée programmatique ; idéal pour ajouter des champs au dernier moment.
  • Typage Zod + infer = payload sécurisé côté client et serveur.
  • Tu peux factoriser submitUserDelete(slug) et la réutiliser partout dans l’app.
  • Les API sont exactement les mêmes que <Form> : tu gardes la même validation, le même cycle loader ⇒ action ⇒ loader.
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