Apprends à créer un compte sécurisé avec React Router 7 et Prisma. Validation, hash de mot de passe et redirection intelligente inclus.
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é
Jusqu’ici nos utilisateurs pouvaient
mais... aucun moyen de créer un compte depuis l’interface. Dans le transcript tu as vu :
login
→ register
slug
au profit de email
password
, email UNIQUE
).Dans cette leçon on consolide ces modifications et on termine le flow d’inscription -- toujours type-safe, toujours full-stack.
Le nouveau schéma exige trois migrations :
1model User {2id Int @id @default(autoincrement())3- firstName String4- lastName String5- age Int6+ firstName String?7+ lastName String?8+ age Int?9slug String @unique10+ email String @unique11+ password String12active Boolean @default(true)13}
1npx 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é unseed
. Choisis la stratégie qui te convient (truncate, valeur par défaut ou migration « step-by-step »).
1import { prisma } from "~/server/db.server";2import { hash } from "bcryptjs";34await prisma.user.create({5data: {6email: "virgile@algomax.fr",7password: await hash("abc123", 10),8slug: "virgile",9},10});
1export const RegisterSchema = z.object({2email: z.string({ required_error: "L’email est obligatoire" })3.email("Format invalide"),4password: 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.
1const [form, fields] = useForm({2lastResult: actionData?.result,3constraint: getZodConstraint(RegisterSchema),4onValidate({ formData }) {5return parseWithZod(formData, { schema: RegisterSchema });6},7});
1<Form method="POST" {...getFormProps(form)} className="space-y-4">2<Field3labelProps={{ children: "Email" }}4inputProps={getInputProps(fields.email, { type: "email" })}5errors={fields.email.errors}6/>7<Field8labelProps={{ children: "Mot de passe" }}9inputProps={getInputProps(fields.password, { type: "password" })}10errors={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
.
POST /register
1export async function action({ request }: ActionFunctionArgs) {2const formData = await request.formData();34// 1. validation + règles asynchrones5const submission = await parseWithZod(formData, {6async: true,7schema: RegisterSchema.superRefine(async (data, ctx) => {8const exists = await prisma.user.findUnique({9where: { email: data.email },10select: { id: true },11});12if (exists) {13ctx.addIssue({14path: ["email"],15code: "custom",16message: "Cet email est déjà utilisé",17});18}19}),20});2122if (submission.status !== "success") {23return data({ result: submission.reply() }, { status: 400 });24}2526// 2. création + hash du mot de passe27const hashed = await hash(submission.value.password, 10);28const slug = submission.value.email.split("@")[0] + crypto.randomUUID().slice(0,4);2930const user = await prisma.user.create({31data: { ...submission.value, password: hashed, slug },32});3334// 3. ouverture de session35const session = await getUserSession({ request });36session.set("userId", String(user.id));3738const url = new URL(request.url);39const redirectTo = url.searchParams.get("redirectTo") ?? "/";40return redirect(redirectTo, {41headers: { "Set-Cookie": await commitSession(session) },42});43}
Points importants du transcript reformulés :
login.tsx
puis renommé
toutes les occurrences de slug
→ email
;checkIfUserExists
travaille désormais sur l’email ;custom issue
et on
renvoie 400
avec submission.reply()
– Conform affiche le message.Même refactoring :
1- slug: z.string(...)2+ email: z.string().email("Format invalide")
Et adapter les appels à checkIfUserExists({ email,… })
.
1<Link to={href("/register")} className="text-gray-800 font-bold">2Register3</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).
1# 1. Démarrer la seed + dev2npx prisma db seed3npm run dev45# 2. Naviguer sur /users ← redirection /login?redirectTo=/users6# 3. Cliquer « Register »7# 4. Créer varkov@google.com / abc1238# 5. Redirigé sur /users (auth OK)
Observe dans Postgres :
1SELECT email, slug FROM "User";2-- email | slug3-- 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 ».
superRefine
: vérifie l’unicité avant de créer l’utilisateur.redirectTo
(/login?redirectTo=/users
).httpOnly
signé avec SESSION_SECRET
.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 ?