[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 |


