React Hook Form Tutorial: Forms, Validation, and Errors

Set up React Hook Form, register fields, add validation rules, and display errors from formState. Includes a Zod resolver example for schema validation.

7 min read

React Hook Form is a library that manages form state through refs instead of per-keystroke state updates, so fields stay uncontrolled and the form re-renders far less. You register each input, attach a submit handler, and read validation errors from a formState object. This tutorial sets it up with built-in rules and a Zod schema.

Install and wire up useForm

The library has no runtime dependencies and supports React 16.8 through 19. Install it, then call the useForm hook and pull out register and handleSubmit.

bashbash
npm install react-hook-form

The register function attaches a field by name and returns the props to spread onto the input. The handleSubmit function wraps your onSubmit callback so validation runs first. Spreading the returned props wires the ref, the change handler, and the name in one step, which is why a React Hook Form input never needs a value or onChange prop.

App.jsxApp.jsx
import { useForm } from "react-hook-form";
export default function SignupForm() {
  const { register, handleSubmit } = useForm();
  function onSubmit(data) {
    alert(JSON.stringify(data));
  }
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="name">Name</label>
      <input id="name" {...register("name")} />
      <button type="submit">Submit</button>
    </form>
  );
}

Type a name and press Submit. handleSubmit collects the field into an object and passes it to onSubmit, so the alert shows the submitted values. The input never needed a value or onChange prop because the library reads it from the DOM.

Add validation rules and show errors

Register accepts a second argument of validation rules that mirror the HTML standard: required, minLength, maxLength, pattern, min, max, and a custom validate function. Passing a string to a rule turns it into the error message.

App.jsxApp.jsx
import { useForm } from "react-hook-form";
 
export default function SignupForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();
 
  function onSubmit(data) {
    alert(JSON.stringify(data));
  }
  // the return below continues this same component

Destructure errors from formState, and the rule message you pass to register becomes errors.name.message for the field that failed validation.

App.jsxApp.jsx
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="name">Name</label>
      <input id="name" {...register("name", { required: "Enter your name." })} />
      {errors.name && <p role="alert">{errors.name.message}</p>}
      <button type="submit">Submit</button>
    </form>
  );
}

Submit the empty form and handleSubmit blocks onSubmit, then fills errors with the field failure. The paragraph renders the message from errors.name.message, and role alert announces it to screen readers. By default validation runs on submit, so users are not interrupted while typing.

Set default values and validation timing

useForm accepts a defaultValues option that seeds every field before the user types, and a mode option that decides when validation runs. The default mode is onSubmit, which checks the whole form once when the user submits. Set mode to onBlur to validate each field as the user leaves it, or onChange to validate while typing.

The onChange mode feels immediate but re-renders more, so save it for small forms or fields where live feedback is the point. After a failed submit, reValidateMode controls when a field with an error is rechecked, and it defaults to onChange.

These options only change timing, not the rules themselves, so the validation you wrote earlier behaves the same in every mode. Start with the default and adjust only when a specific field needs earlier feedback.

Add a schema with Zod resolver

For many rules or shared schemas, swap the built-in rules for a schema library. Install the resolvers package and Zod, then define the schema once.

bashbash
npm install @hookform/resolvers zod

The resolver package bridges Zod to React Hook Form. Next, define the schema with Zod's object and string helpers below.

index.jsindex.js
import { z } from "zod";
 
const schema = z.object({
  email: z.string().email("Enter a valid email address."),
});

Pass the schema to useForm through the resolver option, and errors.email.message carries Zod's message exactly like the built-in rules. The full resolver setup and more schema patterns live in how to validate React forms with Zod.

Choose the library over hand-written forms

Reach for the library when a form has many fields, complex rules, or performance-sensitive lists. It removes the boilerplate of values and errors state while keeping the form fast.

  • Many fields with repeated validation rules.
  • Forms inside lists that should not re-render on every keystroke.
  • Schema validation you want to share between client and server.

For small forms, plain state is often simpler, and React form validation without a library covers that hand-written path. For a full comparison against another popular library, see React Hook Form vs Formik.

Rune AI

Rune AI

Key Insights

  • Install react-hook-form and call useForm.
  • Spread register into each input to connect it.
  • Pass rules to register for built-in validation.
  • Read messages from formState.errors.
  • Use a resolver with Zod for schema validation.
RunePowered by Rune AI

Frequently Asked Questions

Is React Hook Form controlled or uncontrolled?

Uncontrolled. Fields are registered with refs and the DOM holds each value, so typing does not re-render the form on every keystroke. Values are collected when the form is submitted.

How do I show a validation error in React Hook Form?

Destructure errors from formState and render a message per field. Use errors.name.message after setting a rule like required with a message string.

Does React Hook Form work with React 19?

Yes. React Hook Form 7.x supports React 16.8 through 19, so it works with current React 19 projects.

Conclusion

React Hook Form registers fields with refs, validates on submit, and reports errors through formState. Add rules to register, then surface each message accessibly.