React Hook Form Tutorial: Build a Membership Registration Form with Validation

by Vincy. Last modified on September 24th, 2025.

When creating forms in a React application, the code complexity is based on the form size. In a previous article, we used a custom form creation method to show a React contact form. It contains several states and handlers exclusively created and managed for the form fields.

If the form is high complex with more number of fields, we have to look for a better alternative to the custom form creation.

React Hook Form is a very good option to work with web forms. This library supports hooks for handling input-change and form-submit events. It also provides built-in properties for form validation.

This tutorial is to learn how to use React hook form to develop forms. We used the features of the React hook form library with its useful hooks and properties.

It also closes the loop by covering the backend api in PHP to process and manage the form inputs.
React Hook Form Registration

Overview of the React Membership Registration Form

This example has a complete workflow for athletes registration using React Hook Form on the frontend and PHP + MySQL on the backend. It highlights the React Hook Form library hooks for form validation and submission.

It collects basic athlete information using a simple, user-friendly form rendered by a React JSX layout. It uses the useForm hook to register form state, input and the validation rules.

It imports Axios to communicate with the backend API. We have seen several React examples that connect to the API via axios to request server resources. For example, in a React file upload script, we posted the file binary via axios.

Part of (UserRegisterform.jsx) – Form layout

import React from "react";
import { useForm } from "react-hook-form";
import { validationRules } from "./Validation-rules";
import { requiredFieldValidation } from "./Validation-rules";
import "./UserRegisterForm.css";
const User_register_form = () => {
  const { register, handleSubmit, formState: { errors }, reset, setError } = useForm();
        ...
        ...
  return (
    <div className="user-register-container">
      <div className="user-register-card">
        <div className="user-register-form-section">
          <div style={{ marginBottom: "24px", width: "425px" }}>
            <h1 className="user-register-title">Athlete Registration Form</h1>
            <p className="user-register-subtitle">
              Thank you for signing up at our club.
              <br />
              Please fill in your information and we will contact you shortly to
              complete your membership.
            </p>
          </div>
          {/* Banner */}
          {banner.message && (
            <div className={`response-message ${banner.type}`}>
              {banner.message}
            </div>
          )}
          {/* Form */}
          <form onSubmit={handleSubmit(onSubmit)} className="user-register-form">
            {/* First Name */}
            <div>
              <label className="form-label">First Name:</label>
              <input
                type="text"
                {...register("firstName", {
                  required: "First name is required",
                  ...validationRules.firstName,
                })}
                className="form-input"
              />
              {errors.firstName && <p className="user-register-error">{errors.firstName.message}</p>}
            </div>
            {/* Last Name */}
            <div>
              <label className="form-label">Last Name:</label>
              <input
                type="text"
                {...register("lastName", {
                  required: "Last name is required",
                  ...validationRules.lastName,
                })}
                className="form-input"
              />
              {errors.lastName && <p className="user-register-error">{errors.lastName.message}</p>}
            </div>
            {/* Email */}
            <div>
              <label className="form-label">Email:</label>
              <input
                type="email"
                {...register("email", {
                  required: "Email is required",
                  ...validationRules.email,
                })}
                className="form-input"
              />
              {errors.email && <p className="user-register-error">{errors.email.message}</p>}
            </div>
            {/* Phone */}
            <div>
              <label className="form-label">Phone:</label>
              <input
                type="tel"
                {...register("phone", {
                  required: "Phone is required",
                  ...validationRules.phone,
                })}
                className="form-input"
              />
              {errors.phone && <p className="user-register-error">{errors.phone.message}</p>}
            </div>
            <div>
              <label className="form-label">Gender:</label>
              <div style={{ display: "flex", gap: "24px" }}>
                <label>
                  <input
                    type="radio"
                    value="male"
                    {...register("gender", requiredFieldValidation)}
                  /> Male
                </label>
                <label>
                  <input
                    type="radio"
                    value="female"
                  {...register("gender", requiredFieldValidation)}
                  /> Female
                </label>
              </div>
              {errors.gender && (
                <p className="user-register-error">{errors.gender.message}</p>
              )}
            </div>
            {/* Weight */}
            <div>
              <label className="form-label">Weight (kg):</label>
              <input
                type="number"
                {...register("weight", {
                  required: "Weight is required",
                  ...validationRules.weight,
                })}
                className="form-input"
              />
              {errors.weight && <p className="user-register-error">{errors.weight.message}</p>}
            </div>
            {/* Height */}
            <div>
              <label className="form-label">Height (cm):</label>
              <input
                type="number"
                {...register("height", {
                  required: "Height is required",
                  ...validationRules.height,
                })}
                className="form-input"
              />
              {errors.height && <p className="user-register-error">{errors.height.message}</p>}
            </div>
            <div>
              <label className="form-label">
                Please describe the history of chronic illness:
              </label>
              <textarea
                {...register("chronicIllness")}
                className="form-input user-register-textarea"
                placeholder="Please describe any chronic illnesses..."
              />
            </div>
            <div>
              <label className="form-label">Does the athlete have allergies?</label>
              <div style={{ display: "flex", gap: "24px" }}>
                <label>
                  <input
                    type="radio"
                    value="yes"
                   {...register("allergies", requiredFieldValidation)}
                  /> Yes
                </label>
                <label>
                  <input
                    type="radio"
                    value="no"
                    {...register("allergies", requiredFieldValidation)}
                  /> No
                </label>
              </div>
              {errors.allergies && (
                <p className="user-register-error">{errors.allergies.message}</p>
              )}
            </div>

            <button type="submit" className="user-register-button">
              REGISTER
            </button>
          </form>
        </div>
      </div>
    </div>
  );
};
export default User_register_form;

Setting Up React Hook Form with Validation

In the above section, each input registers the validation rules’ property defined for the fields. The rules are defined for the name, email, and phone fields in a separate JavaScript file as shown in the below code.

This JS is imported into the JSX, where the form fields are registered with React hook form. The email and phone fields are going through a pattern validation based on regular expression.

React Hook Form Validation

validation-rules.js

// validationRules.js
export const validationRules = {
  firstName: {
    minLength: { value: 2, message: "Must have at least 2 characters" },
  },
  lastName: {
    minLength: { value: 2, message: "Must have at least 2 characters" },
  },
  email: {
    pattern: {
      value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
      message: "Must match a valid email format (abc@domain.com)",
    },
  },
  phone: {
    pattern: {
      value: /^[\d\s\-+()]{10,}$/,
      message:
        "Must be at least 10 characters and only digits, spaces, -, +, (, ) allowed",
    },
  },
  weight: {
    min: { value: 20, message: "Weight must be at least 20kg" },
    max: { value: 300, message: "Weight cannot exceed 300kg" },
  },
  height: {
    min: { value: 100, message: "Height must be at least 100cm" },
    max: { value: 250, message: "Height cannot exceed 250cm" },
  },
};
export const requiredFieldValidation = { required: "required" };

Submitting Form Data to a PHP Backend Using Axios

React Hook form’s useForm provide a built-in object for handleSubmit. It receives the JS callback to be called on form submit.

This example uses Axios to make a post request to an api endpoint. It prepares the UI element to show the response text in a ribbon like banner.

It handles the types of errors and their messages to update and render the response banner.

Part of (UserRegisterForm.jsx) with state definition and handleSubmit

import React from "react";
import axios from "axios";
import { validationRules } from "./Validation-rules";
import { requiredFieldValidation } from "./Validation-rules";
import "./UserRegisterForm.css";
const User_register_form = () => {
  const { register, handleSubmit, formState: { errors }, reset, setError } = useForm();
  const [banner, setBanner] = React.useState({ message: "", type: "" });
  const onSubmit = async (data) => {
    try {
      const response = await axios.post(
        "http://localhost/react-hook-form-action/register-action-ajax.php",
        new URLSearchParams(data),
        { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
      );
      const result = response.data;
      if (result.status === "error") {
        setError("email", { type: "manual", message: result.message });
        setBanner({ message: result.message, type: "error" });
      } else {
        setBanner({ message: result.message, type: "success" });
        reset();
      }
    } catch (error) {
      console.error("Error:", error);
      setBanner({ message: "Something went wrong.", type: "error" });
    }
  };

Processing the Registration Data in PHP

This is usual PHP file which processes the form data posted via axios. This PHP API endpoint allows the origin where the React app is running.

Path to your api/react-hook-form-action/register-action-ajax.php

<?php
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: http://localhost:5173");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");

if(empty($_POST)) {
    echo json_encode(["status" => "error", "message" => "No data received"]);
    exit;
}
include __DIR__ . '/db.php';

// Collect data
$firstName = $_POST['firstName'] ?? '';
$lastName = $_POST['lastName'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$gender = $_POST['gender'] ?? '';
$weight = $_POST['weight'] ?? '';
$height = $_POST['height'] ?? '';
$chronicIllness = $_POST['chronicIllness'] ?? '';
$allergies = $_POST['allergies'] ?? '';

// Check if email already exists
$sql = "SELECT id FROM tbl_user WHERE email = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows > 0) {
    echo json_encode(["status" => "error", "message" => "Email already exists"]);
    exit;
}

// Insert new record
$sql = "INSERT INTO tbl_user (first_name, last_name, email, phone, gender, weight, height, chronic_illness, allergies, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";

$stmt = $conn->prepare($sql);
$stmt->bind_param("sssssdsss", $firstName, $lastName, $email, $phone, $gender, $weight, $height, $chronicIllness, $allergies);

if ($stmt->execute()) {
    echo json_encode(["status" => "success", "message" => "Thank you for registering!"]);
} else {
    echo json_encode(["status" => "error", "message" => "Database error: " . $conn->error]);
}

Checking Email Uniqueness and Saving to the Database

In the above PHP script, it validates that the posted email is already registered. It compares the database entries and confirms the uniqueness of the email address.

If an email already exists, then the registration process will be stopped. The API will respond with a message alert in JSON format.

On the React end, it parses the JSON object and updates the layout to re-render the banner with the “Email already exists” message.

Handle Email Unique Check

Handling Server Responses and Displaying Feedback in React

Once the form data is stored successfully, the API will respond with a “Thank you” message for the React layout.

React Hook Handle Server Response

How to setup?

Download the React hook form example code and unzip it into your environment. After that, run the npm install to have all the dependancies in the package.json.

"dependencies": {
  "axios": "^1.12.2",
  "react": "^19.1.1",
  "react-dom": "^19.1.1",
  "react-hook-form": "^7.63.0"
}

Important: This bundle has the /react-hook-form-action/ directory, which contains the PHP API endpoint code. Keep this directory in your PHP environment and change the target in the Axios request.

Then, execute npm run dev command to start the development server to run this example.

Download

Vincy
Written by Vincy, a web developer with 15+ years of experience and a Masters degree in Computer Science. She specializes in building modern, lightweight websites using PHP, JavaScript, React, and related technologies. Phppot helps you in mastering web development through over a decade of publishing quality tutorials.

Leave a Reply

Your email address will not be published. Required fields are marked *

↑ Back to Top

Share this page