Introduction

Forms are an essential part of most web applications — login, registration, profile update, checkout, and more. But building forms repeatedly in every component can lead to duplicated code, messy logic, and difficult maintenance. A better way is to create reusable form components supported by a custom hook that handles state, validation, input changes, and submission logic. This makes your React code cleaner, reusable, and easier to maintain. In this article, you will learn how to build a reusable form component step-by-step using simple and natural language.

Why Build Reusable Form Components?

Creating reusable form components saves time and improves consistency.

Benefits

Designing the useForm Custom Hook

A custom hook lets you extract form logic into a reusable function.

Basic Structure of the Hook

import { useState } from "react";

export function useForm(initialValues, onSubmit) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues({ ...values, [name]: value });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const validationErrors = validate(values);

    if (Object.keys(validationErrors).length === 0) {
      onSubmit(values);
    } else {
      setErrors(validationErrors);
    }
  };

  return { values, errors, handleChange, handleSubmit };
}

What This Hook Does

You can now reuse this across multiple form components.

Creating a Separate Validation Function

Validation logic should be separate so it is reusable.

Example Validation

export function validate(values) {
  const errors = {};

  if (!values.name) {
    errors.name = "Name is required";
  }

  if (!values.email) {
    errors.email = "Email is required";
  }

  return errors;
}

Why Separate Validation?

Building a Reusable Form Component

Now create a form component that uses the custom hook.

Example Form

import { useForm } from "./useForm";
import { validate } from "./validate";

function UserForm() {
  const onSubmit = (values) => {
    console.log("Form submitted:", values);
  };

  const { values, errors, handleChange, handleSubmit } = useForm(
    { name: "", email: "" },
    onSubmit
  );

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={values.name}
          onChange={handleChange}
        />
        {errors.name && <p className="error">{errors.name}</p>}
      </div>

      <div>
        <label>Email:</label>
        <input
          type="email"
          name="email"
          value={values.email}
          onChange={handleChange}
        />
        {errors.email && <p className="error">{errors.email}</p>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
}

export default UserForm;

Why This Is Reusable

Making Form Fields Reusable with a Custom Input Component

We can go further by creating a reusable input field.

Example

function InputField({ label, name, value, onChange, error }) {
  return (
    <div>
      <label>{label}</label>
      <input name={name} value={value} onChange={onChange} />
      {error && <span className="error">{error}</span>}
    </div>
  );
}

Use in Form

<InputField
  label="Name"
  name="name"
  value={values.name}
  onChange={handleChange}
  error={errors.name}
/>

Benefits

Adding Loading States and Disable Submit Button

Real-world forms need loading state while submitting.

Example Update

const [loading, setLoading] = useState(false);

const handleSubmit = async (e) => {
  e.preventDefault();
  const validationErrors = validate(values);

  if (Object.keys(validationErrors).length === 0) {
    setLoading(true);
    await onSubmit(values);
    setLoading(false);
  } else {
    setErrors(validationErrors);
  }
};

User Experience Improvement

Reusing the Form Hook for Multiple Forms

Now you can build many forms using the same logic.

Example Login Form

const { values, errors, handleChange, handleSubmit } = useForm(
  { email: "", password: "" },
  loginUser
);

Example Registration Form

const { values, errors, handleChange, handleSubmit } = useForm(
  { name: "", email: "", password: "" },
  registerUser
);

Just change the initial values and validation functions.

Best Practices for Building Reusable Forms

Conclusion

Building reusable form components in React with custom hooks helps you avoid repetitive code, improve maintainability, and create cleaner UI structures. By separating form logic, validation, and UI components, you can build multiple forms faster and with fewer bugs. With this pattern, your React applications become more scalable, efficient, and developer-friendly — perfect for both small projects and large-scale applications.