React Hook Form Reference

React Hook Form is a performant form library for form state, validation, and submission handling with minimal re-renders.

Imports and setup


interface FormData {
  email: string;
}

export default function App() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>();

  const onSubmit = (data: FormData) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <div className="space-y-2">
        <label htmlFor="email">Email</label>
        <input id="email" type="email" {...register("email", { required: "Email is required" })} />
        {errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

Validation and state

Attach simple validation rules in register().

register("username", {
  required: "Username is required",
  minLength: { value: 3, message: "Minimum 3 characters" },
  maxLength: { value: 20, message: "Maximum 20 characters" },
});

register("phone", {
  pattern: { value: /^[0-9]{10}$/, message: "Invalid phone number" },
});

register("confirmPassword", {
  validate: (value, formValues) =>
    value === formValues.password || "Passwords don't match",
});

Use errors for field messages, isSubmitting to disable submit buttons, isDirty for changed forms, and isValid when the selected validation mode tracks validity.

Custom components

Use Controller for custom controls that do not behave like native inputs, such as Select, Checkbox, RadioGroup, and similar UI primitives.


function PriorityField() {
  const { control } = useForm({ defaultValues: { priority: "medium" } });

  return (
    <Controller
      name="priority"
      control={control}
      rules={ { required: "Choose a priority" } }
      render={({ field }) => (
        <select {...field}>
          <option value="">Priority</option>
          <option value="low">Low</option>
          <option value="medium">Medium</option>
          <option value="high">High</option>
        </select>
      )}
    />
  );
}

Use watch for conditional fields, reset() after successful submission, and setValue() for programmatic updates.

const { watch, register, reset, setValue, getValues } = useForm();

const showOptional = watch("showOptional");

{showOptional && <input {...register("optionalField")} />}

reset();
reset({ name: "Jane" });
setValue("name", "John");
const values = getValues();

Zod validation

For reusable validation, combine React Hook Form with Zod and @hookform/resolvers/zod.

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.email({ error: "Invalid email" }),
  password: z.string().min(8, { error: "Minimum 8 characters" }),
});

type FormData = z.infer<typeof schema>;

const form = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: { email: "", password: "" },
});

Best practices

Always provide defaultValues, use Controller for custom UI controls, display errors.fieldName.message below inputs, disable submit while isSubmitting, call reset() after successful submissions, and prefer Zod for validation that spans fields or needs a reusable runtime schema.