About

evolving · Apr 22, 2026 · 2 min read

React Hook Form

React Hook Form (RHF) is a form library for React. Its main pitch is performance: most form libraries store input values in React state, which triggers a re-render on every keystroke. React Hook Form uses uncontrolled inputs by default, meaning the DOM itself holds the values and React only gets involved when needed (validation, submit). Fewer re-renders, faster forms.


Basic usage

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

type FormData = {
  name: string
  email: string
}

function BookingForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>()

  const onSubmit = (data: FormData) => {
    console.log(data) // { name: '...', email: '...' }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name', { required: 'Name is required' })} />
      {errors.name && <p>{errors.name.message}</p>}

      <input {...register('email', { required: true })} />
      <button type="submit">Submit</button>
    </form>
  )
}

register wires the input into the form system. handleSubmit validates before calling your submit handler. formState.errors holds validation errors per field.


Key concepts

Uncontrolled by default

RHF reads input values from the DOM on demand instead of keeping them in React state. This avoids the re-render-on-every-keystroke problem that plagues controlled forms in larger applications.

When you do need a controlled input (for example, a custom date picker or a UI library component), RHF provides the Controller component as a wrapper.

Schema validation with Zod

RHF integrates with Zod via @hookform/resolvers. Instead of writing validation rules field-by-field in register(), you define one Zod schema and pass it as a resolver:

import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const schema = z.object({
  name: z.string().min(1, 'Required'),
  email: z.string().email('Invalid email'),
})

const form = useForm({
  resolver: zodResolver(schema),
})

This keeps all validation logic in the schema — one source of truth that also gives you the TypeScript type.

watch and setValue

RHF lets you observe field values reactively with watch() (useful for live summaries or conditional fields) and update fields programmatically with setValue().

formState

formState exposes useful flags beyond just errors: isSubmitting, isDirty (user has changed something), isValid, and touchedFields. Useful for controlling button states and UI feedback.


When not to use RHF

RHF shines for large, complex forms where re-render performance matters. For simple forms with two or three fields, plain controlled inputs with useState are often clearer and fine.


Sources

  1. React Hook Form Docs
  2. RHF + Zod resolver