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

Dedsec

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

  1. Introduction & Requirements
  2. Project Setup
  3. Installing Dependencies
  4. Folder Structure
  5. Creating Reusable Input Components
  6. Building the Login Form
  7. Building the Registration Form
  8. Validations & Error Handling
  9. Connecting to a Backend (Optional)
  10. 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 the App 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 parent FormProvider.
  • 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 the zodResolver.
  • onSubmit: Handles submission logic (placeholder console.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:

  1. 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.
  2. Conditional Validations You can add custom refinements or conditions (e.g., requiring a phone number only if a country requires it).
  3. 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:

  1. Login API
    • Send email and password to your backend’s /login endpoint.
    • Receive tokens (JWT or similar) or session cookies for authentication.
  2. Registration API
    • Send name, email, password, etc., to /register.
    • Handle user creation, email verification, and other flows.
  3. 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:

  1. Reusable Components: Create generic input components to standardize UI and reduce code repetition.
  2. Validation Made Easy: Use schema-based validators like Zod or Yup for robust form validation.
  3. User Experience: Display clear error messages and guide users through the login and registration processes.
  4. 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!