DEV Community

Cover image for Building Forms with React Hook Form (Part 2)
Dhokia Rinidh
Dhokia Rinidh

Posted on

Building Forms with React Hook Form (Part 2)

Hey guys!

I kept exploring React Hook Form (RHF) over the past week, and something became very clear: it’s one of the most performant and thoughtful form libraries in the React ecosystem. After sharing a set of beginner-friendly lessons in my previous post, I’m back with more discoveries that will hopefully save you from some debugging headaches.

If you haven’t read the first article, you can find it here:

➡️ Building Forms Using React Hook Form #1

Now—on to the juicy bits.


More Lessons & Tips

1. useWatch() and re-renders

useWatch() lets you track field values without re-rendering the entire form, but only if you watch fields narrowly and intentionally. Watching a large object, an array, or a whole form node can cause your component to re-render far more often than needed.

Bad example — watching the entire array:

const fieldGroupsArray = useWatch({
  name: "fieldGroupsArray", // ❌ triggers re-render whenever anything changes in the array
});
Enter fullscreen mode Exit fullscreen mode

Better — watch only what you need:

const conditionalValue = useWatch({
  name: "fieldGroupsArray.0.conditionalField", // ✅ only reacts to this field
});
Enter fullscreen mode Exit fullscreen mode

Note that:

  • watch() re-renders the form where useForm() lives
  • useWatch() re-renders only the component calling it

Use the second option for large form structures or repeated field groups. Your CPU will thank you.

2. Subscribing to formState can be expensive

Accessing formState is powerful, but it has consequences. Some values trigger re-renders more often than others:

  • dirtyFields updates on every change while typing → constant re-renders
  • touchedFields updates when a user focuses on the fields
  • errors changes when validation runs. Not a problem unless your validation runs endlessly

Access only what you need. If you destructure everything because of "why not"—you might introduce performance issues blindly.

3. When using Zod with dynamic fields, be cautious with shouldUnregister

Like me, if you’re a user of Zod + RHF's dynamic conditional fields, think twice before enabling:

useForm({ shouldUnregister: true }) // ⚠️ Caution
Enter fullscreen mode Exit fullscreen mode

Unregistered fields get completely removed from the form state, meaning you may lose default values that are still relevant for validation. This can lead to unexpected Zod validation failures, especially when showing/hiding fields conditionally.

The shouldUnregister flag immediately removes fields from RHF's internal state if it is not registered to any form input. In dynamic schemas, keeping fields registered helps preserve data consistency.

4. About using <Controller> / useController() when the standard register() isn't enough

Scenarios where it is suitable:

  1. External UI libraries with no direct ref, value, or onChange props exposed.
  2. Controlled inputs (e.g. Chakra UI <Select />) which possess their own separate internal state
  3. Complex custom components where you want granular control over which elements receive {...field}

Example:

// You may bind only <input> to form state instead of rerendering label + error
<CustomField>
  <label>Username</label>
  <input {...field} />
  <p role="alert">{errorMessage}</p>
</CustomField>
Enter fullscreen mode Exit fullscreen mode

This reduces noise, unnecessary renders, and keeps the field logic predictable.

Wrapping Up

Not all of this may be new to you — maybe you nodded through every section, maybe some of it made you open your project and fix things immediately. Either way, if we both improved our understanding today, that’s already a win.

Feel free to drop suggestions, corrections, or even healthy criticism in the comments.

More experiments coming soon 😉

Top comments (0)