Back to list
ReactHookreact-hook-formuseFormForm

[React][Hook] Build Forms with react-hook-form (useForm)

Replace piles of useState and onChange handlers with useForm from react-hook-form for faster forms, less re-rendering, and built-in validation.

1. Environment

  • OS: Windows 11 / macOS
  • Frontend library: React.js / Next.js
  • Package: react-hook-form
  • Terminal: PowerShell (Windows) / zsh (macOS)

2. Problem

Sign-up, login, surveys - forms are unavoidable in web apps. Building them with plain React alone often turns ugly fast.

If you have ten inputs (email, password, name, phone, |–, you also end up with ten useState calls and ten onChange handlers.

Worse is performance: every keystroke in the email field triggers useState and re-renders the whole form component dozens of times.

3. Cause

That cost comes from React?’s default form style: controlled components.

  • Controlled-component limits: The input value is tied to React state. Each keystroke updates state, so React re-draws the UI.
  • Messy validation: Rules like “password must be 8+ characters|–or “must look like an email,|–plus error messages, explode into long, hard-to-read code when written by hand.

4. Solution

1) Install the library

Windows / macOS:

# npm
npm install react-hook-form

# yarn
yarn add react-hook-form

# pnpm (recommended)
pnpm add react-hook-form

2) Old way (useState) vs modern way (useForm)

Past approach: many useStates, flicker on every keystroke

// More fields |--more state and handlers forever
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');

const handleEmailChange = (e) => {
  setEmail(e.target.value);
  if (!e.target.value.includes('@')) setEmailError('Invalid email format.');
};

Modern approach: react-hook-form for less code and fewer renders

import React from 'react';
import { useForm } from 'react-hook-form';

function LoginForm() {
  // 1. Pull what you need from useForm
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  // 3. Runs after a successful submit
  const onSubmit = (data) => {
    console.log('Clean payload for the server:', data);
  };

  return (
    // 2. Wire handleSubmit to the form
    <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
      <div>
        <input
          placeholder="Email"
          {...register('email', {
            required: 'Email is required.',
            pattern: {
              value: /\S+@\S+\.\S+/,
              message: 'Enter a valid email.',
            },
          })}
        />
        {errors.email && <span className="text-red-500">{errors.email.message}</span>}
      </div>

      <div>
        <input
          type="password"
          placeholder="Password"
          {...register('password', {
            required: 'Please enter a password.',
            minLength: { value: 8, message: 'Password must be at least 8 characters.' },
          })}
        />
        {errors.password && (
          <span className="text-red-500">{errors.password.message}</span>
        )}
      </div>

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

export default LoginForm;

register defines validation in one place; when a rule fails, the hook fills errors for you.

5. Summary

From a simple login box to a sign-up form with dozens of fields, useForm shortens development time and keeps the UI snappy.

useState react-hook-form (useForm)
Rendering Controlled (re-render on each keystroke) Uncontrolled (work mainly on submit)
Code size State + handler per field Mostly one register line per field
Validation Hand-written Options like required, minLength
Performance Heavy forms get slow Far fewer renders while typing

Related posts