Building the Registration Flow

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.

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