React Login and Registration Form Tutorial: A Step-by-Step Guide (2024)

Pedro Machado / December 28, 2024
8 min read •
Description
A thorough tutorial on how to build a secure and user-friendly login and registration form in React, including best practices and up-to-date code.
Getting Started with a React Login/Registration Form: A Step-by-Step Tutorial
Welcome to this comprehensive guide on building a modern, secure, and user-friendly login and registration form in React. In this tutorial, we’ll walk you through setting up your React project, installing dependencies, creating reusable form components, handling validations, and integrating best practices for authentication flows. By the end, you’ll have a solid understanding of how to implement a scalable login/registration form in React—an essential feature for most web applications.
(Prefer watching? Check out my YouTube channel Pedrotechnologies for video tutorials and demonstrations!)
Table of Contents
- Introduction & Requirements
- Project Setup
- Installing Dependencies
- Folder Structure
- Creating Reusable Input Components
- Building the Login Form
- Building the Registration Form
- Validations & Error Handling
- Connecting to a Backend (Optional)
- Conclusion
1. Introduction & Requirements
A login/registration system is the cornerstone of any modern application. It securely authenticates users, protects restricted content, and collects essential information during registration. Here are a few points to keep in mind before you begin:
- Basic React Knowledge: Familiarity with React components and hooks (e.g.,
useState
,useEffect
). - Node.js & npm (or yarn/pnpm/bun): For managing packages and running a local development server.
- Optional: A backend API for actual authentication. In this tutorial, we’ll demonstrate form submission using mock APIs or placeholders.
Technologies We’ll Use:
- React 18+: Latest version for building user interfaces.
- React Hook Form (optional but recommended): For simplified form state management and validation.
- Zod or Yup (optional): For schema-based validations.
- Tailwind CSS (optional): For styling (though you can use any CSS framework or custom CSS).
2. Project Setup
To get started with a React application, we’ll use Vite for a fast and minimal project setup. If you already have a React + Vite project, skip this step.
Scaffolding a New Project
npm create vite@latest my-auth-app -- --template react
# or
pnpm create vite my-auth-app -- --template react
# or
yarn create vite my-auth-app --template react
Follow the interactive prompts, and once the project is created, navigate to the directory:
cd my-auth-app
Then install dependencies (if not already done):
npm install
# or
pnpm install
# or
yarn install
Finally, start the development server:
npm run dev
Your React application will be available at http://localhost:5173
by default.
3. Installing Dependencies
For this tutorial, we recommend using React Hook Form for form handling and Zod for validation. If you prefer a different stack (like Formik/Yup), feel free to adapt accordingly.
npm install react-hook-form zod
# or
pnpm add react-hook-form zod
# or
yarn add react-hook-form zod
React Hook Form helps in managing form states and validations without re-rendering the entire component tree. Zod is a schema-based validator that offers a clean and type-safe approach to data validation.
4. Folder Structure
Organize your files for better maintainability. Below is a sample folder structure:
my-auth-app/
├── public/
├── src/
│ ├── components/
│ │ ├── forms/
│ │ │ └── TextInput.tsx
│ │ ├── LoginForm.tsx
│ │ └── RegisterForm.tsx
│ ├── pages/
│ │ ├── LoginPage.tsx
│ │ └── RegisterPage.tsx
│ ├── App.tsx
│ └── main.tsx
├── index.html
└── ...
Key Folders & Files:
components/
: For reusable components (e.g., custom inputs, buttons, forms).pages/
: For page-level components (e.g., login page, registration page).App.tsx
: Your main application component where routes or layout might live.main.tsx
: Entry point for rendering theApp
component.
5. Creating Reusable Input Components
Reusable input components help maintain a consistent look and feel across your application. Below is an example TextInput
component using React Hook Form.
// src/components/forms/TextInput.tsx
import { useFormContext } from "react-hook-form";
interface TextInputProps {
label: string;
name: string;
type?: string;
placeholder?: string;
}
export function TextInput({
label,
name,
type = "text",
placeholder,
}: TextInputProps) {
const {
register,
formState: { errors },
} = useFormContext();
return (
<div className="mb-4">
<label className="block mb-1 font-semibold" htmlFor={name}>
{label}
</label>
<input
id={name}
type={type}
placeholder={placeholder}
{...register(name)}
className="border rounded px-3 py-2 w-full"
/>
{errors[name] && (
<p className="text-red-500 text-sm mt-1">
{String(errors[name]?.message)}
</p>
)}
</div>
);
}
Explanation:
useFormContext
: Inherits methods and state from the parentFormProvider
.label
: Descriptive text for the input field.register
: Binds the input to form state management.errors[name]
: Shows validation errors tied to the specific field.
6. Building the Login Form
Let’s create a LoginForm
component that captures username (or email) and password. We’ll use react-hook-form
’s FormProvider
to pass the form context down to our reusable input components.
// src/components/LoginForm.tsx
import { FormProvider, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { TextInput } from "./forms/TextInput";
// Define schema using Zod
const loginSchema = z.object({
email: z.string().email("Please enter a valid email."),
password: z.string().min(6, "Password must be at least 6 characters."),
});
type LoginFormValues = z.infer<typeof loginSchema>;
export function LoginForm() {
const methods = useForm<LoginFormValues>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: "",
password: "",
},
});
const onSubmit = (data: LoginFormValues) => {
// This is where you’d typically call your login API
console.log("Login data submitted:", data);
};
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} className="max-w-sm">
<h2 className="text-2xl font-bold mb-4">Login</h2>
<TextInput
name="email"
label="Email"
type="email"
placeholder="john@doe.com"
/>
<TextInput
name="password"
label="Password"
type="password"
placeholder="******"
/>
<button
type="submit"
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Log In
</button>
</form>
</FormProvider>
);
}
Explanation:
loginSchema
: Defines validation rules (e.g., email must be valid, password must be at least 6 characters).useForm
: Initializes the form with default values and thezodResolver
.onSubmit
: Handles submission logic (placeholderconsole.log
in this example, but typically an API call).
7. Building the Registration Form
The registration form usually requires additional fields such as name, confirm password, or phone number. Let’s create a RegisterForm
component in a similar fashion.
// src/components/RegisterForm.tsx
import { FormProvider, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { TextInput } from "./forms/TextInput";
const registerSchema = z
.object({
name: z.string().min(2, "Name must be at least 2 characters."),
email: z.string().email("Please enter a valid email."),
password: z.string().min(6, "Password must be at least 6 characters."),
confirmPassword: z
.string()
.min(6, "Password must be at least 6 characters."),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords must match.",
path: ["confirmPassword"],
});
type RegisterFormValues = z.infer<typeof registerSchema>;
export function RegisterForm() {
const methods = useForm<RegisterFormValues>({
resolver: zodResolver(registerSchema),
defaultValues: {
name: "",
email: "",
password: "",
confirmPassword: "",
},
});
const onSubmit = (data: RegisterFormValues) => {
console.log("Registration data submitted:", data);
// Typically call a registration API here
};
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} className="max-w-sm">
<h2 className="text-2xl font-bold mb-4">Register</h2>
<TextInput name="name" label="Full Name" placeholder="John Doe" />
<TextInput
name="email"
label="Email"
type="email"
placeholder="john@doe.com"
/>
<TextInput
name="password"
label="Password"
type="password"
placeholder="******"
/>
<TextInput
name="confirmPassword"
label="Confirm Password"
type="password"
placeholder="******"
/>
<button
type="submit"
className="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
>
Sign Up
</button>
</form>
</FormProvider>
);
}
Explanation:
refine
: Custom validation rule to ensure passwords match.confirmPassword
: Additional field to confirm user’s password.
8. Validations & Error Handling
We’ve used Zod to handle validations, showing error messages near each field. Here are some important tips:
- Client-Side vs. Server-Side Validation Always validate data on the server for security, even if you do client-side validation for a better UX.
- Conditional Validations You can add custom refinements or conditions (e.g., requiring a phone number only if a country requires it).
- Display Errors Strategically Show error messages near the respective field to guide users.
9. Connecting to a Backend (Optional)
While this tutorial focuses on the frontend, in a real-world scenario you’d integrate these forms with an API:
- Login API
- Send
email
andpassword
to your backend’s/login
endpoint. - Receive tokens (JWT or similar) or session cookies for authentication.
- Send
- Registration API
- Send
name
,email
,password
, etc., to/register
. - Handle user creation, email verification, and other flows.
- Send
- Storing Auth State
- Use React Context or a state management library (e.g., Redux, Zustand) to store user information globally.
- Persist tokens or session info in cookies or localStorage with caution.
10. Conclusion
In this tutorial, we covered how to build a Login and Registration form in React, leveraging React Hook Form for state management and Zod for schema validations. Key takeaways include:
- Reusable Components: Create generic input components to standardize UI and reduce code repetition.
- Validation Made Easy: Use schema-based validators like Zod or Yup for robust form validation.
- User Experience: Display clear error messages and guide users through the login and registration processes.
- Extensibility: Integrate with a backend API for a complete authentication workflow.
Next Steps:
- Connect your forms to a real backend and store authentication states (e.g., tokens) securely.
- Implement advanced features like password resets, email confirmations, and social logins if needed.
- Consider using UI libraries (Tailwind, MUI, Chakra UI) for more polished styling.
With these forms set up, you have a solid foundation for handling user authentication in modern React applications. Remember to keep your security measures up-to-date, and don’t hesitate to add custom validations to fit your project’s needs.
For more tutorials and advanced topics, be sure to check out my YouTube channel Pedrotechnologies.
Happy coding!