Ajout de l'inscription

Apprends à créer un compte sécurisé avec React Router 7 et Prisma. Validation, hash de mot de passe et redirection intelligente inclus.

5 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 l’inscription n’était pas encore disponible ?

Jusqu’ici nos utilisateurs pouvaient

  1. se connecter / se déconnecter,
  2. naviguer dans les routes protégées,

mais... aucun moyen de créer un compte depuis l’interface. Dans le transcript tu as vu :

  • on a dupliqué la page loginregister
  • on a abandonné le champ slug au profit de email
  • le modèle Prisma a reçu deux nouvelles colonnes (password, email UNIQUE).

Dans cette leçon on consolide ces modifications et on termine le flow d’inscription -- toujours type-safe, toujours full-stack.


Étape 1 – Mettre à jour le modèle Prisma

Le nouveau schéma exige trois migrations :

prisma/schema.prisma
1
model User {
2
id Int @id @default(autoincrement())
3
- firstName String
4
- lastName String
5
- age Int
6
+ firstName String?
7
+ lastName String?
8
+ age Int?
9
slug String @unique
10
+ email String @unique
11
+ password String
12
active Boolean @default(true)
13
}
Terminal
1
npx prisma migrate dev --name add_email_and_password

Si la table contient déjà des lignes, Postgres refuse « NOT NULL sans valeur par défaut ». Dans la vidéo on a vidé la table (prisma.user.deleteMany()), puis relancé un seed. Choisis la stratégie qui te convient (truncate, valeur par défaut ou migration « step-by-step »).


Seed avec des mots de passe hachés

prisma/seed.ts
1
import { prisma } from "~/server/db.server";
2
import { hash } from "bcryptjs";
3
4
await prisma.user.create({
5
data: {
6
email: "virgile@algomax.fr",
7
password: await hash("abc123", 10),
8
slug: "virgile",
9
},
10
});

Étape 2 – Déclarer le schéma Zod de l’inscription

app/routes/register.tsx
1
export const RegisterSchema = z.object({
2
email: z.string({ required_error: "L’email est obligatoire" })
3
.email("Format invalide"),
4
password: z.string({ required_error: "Mot de passe obligatoire" })
5
.min(6, "6 caractères minimum"),
6
});

On ne demande plus le slug ; il sera généré coté serveur.


Étape 3 – Formulaire côté client avec Conform

app/routes/register.tsx
1
const [form, fields] = useForm({
2
lastResult: actionData?.result,
3
constraint: getZodConstraint(RegisterSchema),
4
onValidate({ formData }) {
5
return parseWithZod(formData, { schema: RegisterSchema });
6
},
7
});
app/routes/register.tsx
1
<Form method="POST" {...getFormProps(form)} className="space-y-4">
2
<Field
3
labelProps={{ children: "Email" }}
4
inputProps={getInputProps(fields.email, { type: "email" })}
5
errors={fields.email.errors}
6
/>
7
<Field
8
labelProps={{ children: "Mot de passe" }}
9
inputProps={getInputProps(fields.password, { type: "password" })}
10
errors={fields.password.errors}
11
/>
12
<button className="btn-primary w-full">Créer mon compte</button>
13
</Form>

Conform injecte required, valide instantanément et place le focus sur le premier champ en erreur – tu n’écris aucun useState.


Étape 4 – Action POST /register

app/routes/register.tsx
1
export async function action({ request }: ActionFunctionArgs) {
2
const formData = await request.formData();
3
4
// 1. validation + règles asynchrones
5
const submission = await parseWithZod(formData, {
6
async: true,
7
schema: RegisterSchema.superRefine(async (data, ctx) => {
8
const exists = await prisma.user.findUnique({
9
where: { email: data.email },
10
select: { id: true },
11
});
12
if (exists) {
13
ctx.addIssue({
14
path: ["email"],
15
code: "custom",
16
message: "Cet email est déjà utilisé",
17
});
18
}
19
}),
20
});
21
22
if (submission.status !== "success") {
23
return data({ result: submission.reply() }, { status: 400 });
24
}
25
26
// 2. création + hash du mot de passe
27
const hashed = await hash(submission.value.password, 10);
28
const slug = submission.value.email.split("@")[0] + crypto.randomUUID().slice(0,4);
29
30
const user = await prisma.user.create({
31
data: { ...submission.value, password: hashed, slug },
32
});
33
34
// 3. ouverture de session
35
const session = await getUserSession({ request });
36
session.set("userId", String(user.id));
37
38
const url = new URL(request.url);
39
const redirectTo = url.searchParams.get("redirectTo") ?? "/";
40
return redirect(redirectTo, {
41
headers: { "Set-Cookie": await commitSession(session) },
42
});
43
}

Points importants du transcript reformulés :

  • duplication minimale : on a copié le fichier login.tsx puis renommé toutes les occurrences de slugemail;
  • checkIfUserExists travaille désormais sur l’email ;
  • si l’email est déjà présent, on ajoute une custom issue et on renvoie 400 avec submission.reply() – Conform affiche le message.

Étape 5 – Mettre à jour la page Login

Même refactoring :

app/routes/login.tsx
1
- slug: z.string(...)
2
+ email: z.string().email("Format invalide")

Et adapter les appels à checkIfUserExists({ email,… }).


Étape 6 – Mettre à jour la navbar

app/root.tsx
1
<Link to={href("/register")} className="text-gray-800 font-bold">
2
Register
3
</Link>

L’icône Logout ajoute maintenant redirectTo=${pathname} pour revenir sur la page courante après la déconnexion (cf. leçon Détruire le cookie).


Étape 7 – Tester le flux complet

Terminal
1
# 1. Démarrer la seed + dev
2
npx prisma db seed
3
npm run dev
4
5
# 2. Naviguer sur /users ← redirection /login?redirectTo=/users
6
# 3. Cliquer « Register »
7
# 4. Créer varkov@google.com / abc123
8
# 5. Redirigé sur /users (auth OK)

Observe dans Postgres :

1
SELECT email, slug FROM "User";
2
-- email | slug
3
-- varkov@google.com | varkov83c1

Le slug unique est généré via crypto.randomUUID().slice(0,4) → pas de collision, fini l’exception « slug déjà pris ».


Points clés à retenir

  • Modèle de données : email unique + password hashé.
  • Zod/Conform : même schéma réutilisé côté client et serveur.
  • superRefine : vérifie l’unicité avant de créer l’utilisateur.
  • Slug généré : plus de champ obligatoire dans le formulaire.
  • Redirect intelligent : on revient sur la page demandée grâce à redirectTo (/login?redirectTo=/users).
  • Sécurité : mot de passe jamais stocké en clair, cookie httpOnly signé avec SESSION_SECRET.
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