Introduction: Why Registration Matters

Welcome back! In the last lesson, you learned how to build a login form and protect certain routes in your React app. Now, let’s take the next step: allowing new users to create their own accounts. Registration is a key part of most web applications. It lets users sign up, create their own profiles, and access features that require authentication.

By the end of this lesson, you will know how to build a registration flow in your React app. This means you will be able to collect user information, send it to your backend, handle errors, and guide users to the next step after registration.

Quick Recap: App Structure and Routing

Before we dive into the registration flow, let’s quickly review how routing is set up in your app. This will help you see where the registration page fits in.

Here is a simplified version of your routing setup:

// src/routes/router.tsx
import { createBrowserRouter } from "react-router-dom";
import App from "../App";
import HomePage from "../pages/HomePage";
import CatalogPage from "../features/catalog/CatalogPage";
import MyShelfPage from "../features/shelf/MyShelfPage";
import LoginPage from "../pages/LoginPage";
import RegisterPage from "../pages/RegisterPage";
import ProtectedRoute from "../components/ProtectedRoute";

export const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "catalog", element: <CatalogPage /> },
      { path: "login", element: <LoginPage /> },
      { path: "register", element: <RegisterPage /> },
      {
        element: <ProtectedRoute />,
        children: [{ path: "shelf", element: <MyShelfPage /> }],
      },
    ],
  },
]);

In this setup, the /register route points to the RegisterPage component. This is where your registration form will live. If you remember from the previous lesson, protected routes (like /shelf) are only accessible to logged-in users.

Building the Registration Form

Let’s look at how the registration form is built. The form is responsible for collecting a username and password from the user.

Here is the main code for the registration form:

// src/features/auth/RegisterForm.tsx
import { useState, FormEvent } from "react";
import { useNavigate } from "react-router-dom";
import { apiClient } from "../../api/client";
import { AxiosError } from "axios";

export default function RegisterForm() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const navigate = useNavigate();

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setError('');
    try {
      // ✅ include name along with username & password
      await apiClient.post(paths.auth('/register'), { name, username, password });
      navigate('/login');
    } catch (err: any) {
      if (err && err.status === 409) {
        setError('Username already exists. Please choose another.');
      } else {
        setError(err?.data?.message || 'Registration failed. Please try again.');
      }
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-sm mx-auto">
      {error && <p className="text-red-400 bg-red-900/50 p-3 rounded-md">{error}</p>}
      <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" required className="w-full bg-slate-800 p-2 rounded-md" />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" required className="w-full bg-slate-800 p-2 rounded-md" />
      <button type="submit" className="w-full bg-sky-500 hover:bg-sky-600 p-2 rounded-md font-semibold">Register</button>
    </form>
  );
}

Explanation:

  • The form uses React’s useState to keep track of the username, password, and any error messages.
  • When the form is submitted, it calls handleSubmit.
  • If registration is successful, the user is redirected to the login page.
  • If there is an error (like a duplicate username), an error message is shown.

What the user sees:

  • Two input fields: one for username, one for password.
  • A Register button.
  • If there’s an error, a message appears above the form.

Let's take a closer look

  • Three useState hooks

    • username / setUsername: Tracks the text input, making the field a controlled component (React holds the source of truth).
    • password / setPassword: Same pattern for the password field.
    • error / setError: Holds a user-visible message shown above the form. It’s cleared before each attempt to avoid stale errors.
  • e.preventDefault()

    • Stops the browser from doing a full page reload on submit.
    • Keeps the flow inside the SPA so we can run async logic (call API, update UI state, navigate) without leaving the React app.
  • useNavigate()

    • Provides an imperative navigation function. After successful registration, we call navigate('/login', { replace: true }) to send the user to Login and replace history (so “Back” doesn’t return to the submitted form state).
  • apiClient.post('/auth/register', { username, password })

    • Encodes JSON, sets headers, and uses the previously resolved base URL from apiUtils.
    • Returns the parsed body. We treat it as a backend envelope and check success.
  • Success path

    • If success === true, we redirect to Login where the user will authenticate (keeping registration and login concerns cleanly separated).
  • Error path

    • If the server signals failure via an envelope or throws with { status, data }, we surface a clear message.
    • We handle duplicate username specifically (common statuses: 400, 409), defaulting to a friendly message if the server doesn’t provide one.
    • All other failures display a generic message to avoid leaking internal errors to users.

This keeps the UI responsive, avoids reloads, and aligns the frontend strictly to the backend’s envelope shape and status codes.

Submitting Registration Data to the API

When the user submits the form, the app needs to send the registration data to the backend API. This is done using the apiClient.post method.

Let’s look at the key part of the code again:

const handleSubmit = async (e: FormEvent) => {
  e.preventDefault();
  setError("");
  try {
    await apiClient.post("/auth/register", { username, password });
    navigate("/login");
  } catch (err) {
    if (err instanceof AxiosError && err.response?.status === 409) {
      setError("Username already exists. Please choose another.");
    } else {
      setError("Registration failed. Please try again.");
    }
  }
};

Explanation:

  • apiClient.post("/auth/register", { username, password }) sends the user’s data to the backend.
  • If the backend returns a 409 status, it means the username is already taken. The app shows a helpful error message.
  • For any other error, a generic error message is shown.

Example output:
If a user tries to register with a username that already exists, they will see:

Username already exists. Please choose another.

If registration is successful, they are redirected to the login page.

Redirecting After Successful Registration

After a user successfully registers, you want to guide them to the next step: logging in. This is handled by the navigate("/login") line in the handleSubmit function.

How it works:

  • When the registration API call succeeds, the app uses the useNavigate hook from React Router to send the user to the login page.
  • This makes the experience smooth and clear for the user.

What the user experiences:

  • After submitting the form with valid data, the registration form disappears and the login page appears.
Summary and What’s Next

In this lesson, you learned how to build a registration flow in your React app. You saw how to:

  • Add a registration route to your app’s router
  • Build a registration form that collects user input
  • Send registration data to the backend API
  • Handle errors and show helpful messages
  • Redirect users to the login page after successful registration

You are now ready to practice these steps yourself. In the next exercises, you will get hands-on experience building and testing the registration flow. This will help you understand how registration, login, and protected routes all work together to create a secure and user-friendly app. Good luck!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal