Skip to main content

Command Palette

Search for a command to run...

React Hook Form Simplified: From Beginner to Expert

Updated
β€’3 min read
React Hook Form Simplified: From Beginner to Expert

πŸš€ What is React Hook Form?

React Hook Form (RHF) is a lightweight library to manage form state in React using hooks. It’s:

  • Super easy to set up

  • Has built-in validation

  • Works well with schema validation tools like Zod


πŸ”§ Basic Setup

First, install the libraries:

npm install react-hook-form
npm install zod @hookform/resolvers

Now, let’s build a simple login form with validation:

import { useForm } from "react-hook-form";

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email", { required: "Email is required" })} placeholder="Email" />
      {errors.email && <p>{errors.email.message}</p>}

      <input {...register("password", { required: "Password is required" })} type="password" placeholder="Password" />
      {errors.password && <p>{errors.password.message}</p>}

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

πŸ’‘ Explanation:

  • register() connects input fields to React Hook Form.

  • handleSubmit() wraps the submit function.

  • errors gives us access to validation error messages.


βœ… Schema Validation with Zod

Zod is a library that lets you define a validation schema. Let’s use it with RHF for cleaner validation.

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

const schema = z.object({
  email: z.string().email("Invalid email format"),
  password: z.string().min(6, "Password must be at least 6 characters"),
});

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} placeholder="Email" />
      {errors.email && <p>{errors.email.message}</p>}

      <input {...register("password")} type="password" placeholder="Password" />
      {errors.password && <p>{errors.password.message}</p>}

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

🎯 Why use Zod?

  • Keeps validation rules outside of JSX

  • Automatically integrates with TypeScript

  • Makes large forms easier to manage


βš™οΈ Handling Server Errors & Loading State

Let’s handle a fake server error like β€œemail already exists” and show a loading button.

const {
  register,
  handleSubmit,
  formState: { errors, isSubmitting },
  setError,
} = useForm({
  resolver: zodResolver(schema),
});

const onSubmit = async (data) => {
  try {
    await new Promise((res) => setTimeout(res, 1000));
    throw new Error(); // simulate server error
  } catch {
    setError("root", { message: "Email already exists!" });
  }
};

πŸ’‘ isSubmitting disables the button while waiting.

πŸ’‘ setError("root", { message }) is used to show global/server errors.


🌟 Advanced Features You Should Know

Here are some powerful tools I also explored:

1. watch()

Tracks real-time values of inputs.

const watchFields = watch(["email", "password"]);

2. reset()

Resets the form values to initial state or clears it.

reset();

3. Controller

Used for custom components (like dropdowns, sliders).

4. useFieldArray

Used for dynamic fields like adding multiple hobbies or phone numbers.

5. FormProvider

Helps share the form logic across multiple child components.


πŸ§ͺ Final Code with Everything Together

Here’s a full form with Zod, server error, isSubmitting, and real-time validation:

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

const schema = z.object({
  email: z.string().email("Enter a valid email"),
  password: z.string().min(6, "Minimum 6 characters"),
});

export default function App() {
  const methods = useForm({
    resolver: zodResolver(schema),
  });

  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    setError,
    watch,
    reset,
  } = methods;

  const onSubmit = async (data) => {
    try {
      await new Promise((r) => setTimeout(r, 1000));
      throw new Error(); // Simulate error
    } catch {
      setError("root", { message: "Email already exists!" });
    }
  };

  return (
    <FormProvider {...methods}>
      <form onSubmit={handleSubmit(onSubmit)}>
        <h2>Signup Form</h2>
        <input {...register("email")} placeholder="Email" />
        {errors.email && <p>{errors.email.message}</p>}

        <input {...register("password")} placeholder="Password" type="password" />
        {errors.password && <p>{errors.password.message}</p>}

        <button disabled={isSubmitting}>
          {isSubmitting ? "Submitting..." : "Submit"}
        </button>

        {errors.root && <p>{errors.root.message}</p>}
      </form>
    </FormProvider>
  );
}

πŸŽ‰ Summary

  • useForm() handles form state easily

  • register() connects inputs

  • formState.errors shows errors

  • isSubmitting shows loading

  • setError() for server-side errors

  • watch() and reset() for real-time interaction

  • Zod + zodResolver() = clean validation