Comment effectuer une mutation côté serveur
Apprends à modifier des données côté serveur avec React Router 7 et la méthode action : ajout, édition, suppression d’utilisateurs, et protection des données sensibles.
Débloquez cette leçon
Entrez votre email pour accéder gratuitement à ce contenu.
Dans cette leçon, on va apprendre à modifier des données — ajouter, éditer ou supprimer un utilisateur — côté serveur avec React Router 7. On verra comment :
- Protéger les données confidentielles et éviter de les exposer côté client
- Déclarer une
actionpour recevoir et traiter un formulaire en POST - Comprendre le cycle loader ⇒ action ⇒ loader pour garder l’UI synchronisée
Qu’est-ce qu’une mutation ?
Une mutation est une opération qui modifie l’état de ton application ou de ta base de données. Par opposition au loader (qui ne lit que des données), l’action va :
- Créer un nouvel enregistrement
- Modifier un enregistrement existant
- Supprimer un enregistrement
Comme on touche à la source de vérité (la DB), ce traitement doit absolument se faire côté serveur pour :
- Protéger les données confidentielles
- Appliquer des règles d’autorisation (ex : seul l’admin peut supprimer un utilisateur)
Loader vs action
Le loader sert à charger des données (GET).
L’action sert à traiter des mutations (POST, PUT, DELETE, etc.).
Déclarer une action dans ta route
Dans React Router 7, on ajoute simplement une fonction action exportée depuis le même fichier que le loader. Exemple minimal :
1import { Form, useLoaderData, type ActionFunctionArgs }2from 'react-router';3import { getUsers, addUser } from '~/users.server';45export async function loader() {6return { users: await getUsers() };7}89export async function action({ request }: ActionFunctionArgs) {10// 1. lire le payload du formulaire11const formData = await request.formData() // ^? FormData API12// 2. extraire la donnée13const name = formData.get('name') as string14// @callout: formData.get peut retourner null si le champ n'existe pas15// 3. modifier la DB16await addUser({ name })17// 4. renvoyer un signe de succès18return { ok: true }19}2021export default function Users() {22const { users } = useLoaderData<typeof loader>()23return (24<>25{/* liste des utilisateurs */}26{/* ... */}27<Form method="POST">28<input name="name" placeholder="Nom d’utilisateur" />29<button type="submit">Ajouter</button>30</Form>31</>32)33}
Pourquoi Form et method="POST" ?
- Par défaut un
<form>HTML utilise la méthode GET ⇒ déclenche leloader. - Avec
method="POST", React Router appelle d’abord tonactionpuis relance leloaderpour mettre à jour l’UI.
Le composant <Form> de React Router
React Router fournit un <Form> amélioré :
1<Form method="POST" className="flex gap-2">2<input3type="text"4name="name"5className="border px-2"6placeholder="Nom d'utilisateur"7/>8<button type="submit" className="bg-blue-600 text-white px-4">9Ajouter un utilisateur10</button>11</Form>
- Gestion automatique du chargement optimisé
- Possibilité d’annuler la requête
- Recharge partielle ou complète selon ta config
Tip
Tu peux aussi utiliser <Form replace> pour remplacer l’historique après l’action.
Code serveur : base de données en mémoire
Pour cet exemple, on simule une DB en mémoire dans app/users.server.ts :
1const db = {2users: [3{ id: 1, name: 'Virgile' },4{ id: 2, name: 'Robert' },5]6}78export async function getUsers() {9await new Promise(r => setTimeout(r, 100))10return db.users11}1213export async function addUser({ name }: { name: string }) {14const newUser = { id: db.users.length + 1, name }15db.users.push(newUser)16return newUser17}
Attention : à chaque redémarrage du serveur, la mémoire est réinitialisée !
Pour la prod, migre vers une vraie base de données (PostgreSQL, MongoDB, …).
Cycle de vie loader ⇆ action
- Chargement initial
loader()read- render de ton composant
- Soumission du formulaire
action()write- relance de
loader()read - re-render
- Résultat
- L’UI est toujours en phase avec la DB
1flowchart LR2load[Page load] -->|GET| loader3loader --> UI[Render UI]4UI -->|POST form| action5action --> loader6loader --> UI
C’est le principe du full-stack data avec React Router.
Points clés
- Une mutation se fait toujours côté serveur
- Déclare
export async function actiondans ta route <Form method="POST">déclenche l’action- Récupère
formDatadepuisrequest - Retourne un JSON ou redirige (
redirect('/users')) - Après l’action, le loader se relance et rafraîchit l’UI
Exercices rapides
-
Supprimer un utilisateur
- Ajoute un bouton « Supprimer » à chaque ligne de la liste
- Envoie un formulaire POST avec
name="_method" value="delete" - Implémente
actionpour détecter_method === 'delete'
-
Éditer un utilisateur
- Sur clic, affiche un champ
<input>prérempli - Envoie
id+nameauaction - Mets à jour
db.usersavec la nouvelle valeur
- Sur clic, affiche un champ
-
Sécuriser l’action
- Simule une vérification d’admin dans
action - Si non autorisé, renvoie
throw new Response("Forbidden", { status: 403 }) - Affiche un message d’erreur dans
ErrorBoundary
- Simule une vérification d’admin dans
↩︎ Pour approfondir la gestion des formulaires et de la validation, consulte le module Formulaires avancés.
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 ?