Your team's React forms are all hand-rolled with useState, and someone proposes adopting React Hook Form. What does a form library actually give you, and how would you decide whether it is worth adopting?
answer
- values are the easy part
- touched, dirty, errors, submit lifecycle
- values kept outside React state
- one shared schema, two enforcement points
- decide per codebase, not per form
basics
~20 sA form library owns values, touched and dirty flags, errors and the submit lifecycle in one store, wires validation to a shared schema, and avoids re-rendering the whole form per keystroke. For one two-field form it is pure overhead; the case is made by many forms, not by one.
solid answer
~50 sWhat a library sells is not "state management for inputs" — that part is a `useState` call. It sells the bookkeeping nobody enjoys rewriting: per-field touched and dirty tracking, an errors shape fed by a shared schema, submit lifecycle flags, dynamic field arrays, focusing the first invalid field, and, in React Hook Form's case, keeping values out of React state so typing in one field does not re-render every other field. Formik popularised the pattern but keeps values in React state, so a large form re-renders per keystroke. I would decide on volume and shape, not on one form: many forms across the app, validation rules that must be shared with the server, repeat/array fields, or a team that keeps solving the touched-flag problem differently each time. Against it: a genuinely small surface, the cost of wrapping every design-system input to work with the library, and the fact that React 19's own action and form primitives already cover simple submit paths.
go deeper
Know that form libraries exist to handle validation, touched and dirty flags and the submit lifecycle, and that a small two-field form does not need one.
Explain the mechanism you are buying — field values held outside React state with per-field subscription — and contrast it with the fully controlled approach where every keystroke re-renders the form.
Give decision criteria from real code: number and shape of forms, field arrays, shared validation schema, the integration cost against a controlled design system, and what a home-grown useForm hook would cover instead.
Own it as a codebase policy: one house pattern, a migration path that does not rewrite working forms, a position on React 19's built-in action path, and the standing rule that the server validates regardless of what the client library does.
## What is actually being outsourced Holding input values is trivial — `useState` does it in a line. What a form library replaces is the second layer that every serious form grows: - **interaction metadata** per field: touched, dirty, validating, and per-form flags like `isSubmitting`, `isSubmitted`, `isValid`; - **an error pipeline** — running rules, mapping their output to fields, deciding the moment each message appears; - **the submit lifecycle** — prevent the default, validate everything, block re-entry, expose pending, merge server-returned field errors back in; - **dynamic shape** — repeatable groups where a user adds and removes rows, each row keeping its own errors and touched state through the reorder; - **focus behaviour** — jumping to the first invalid field on a failed submit. Every team writes some version of this. The question is whether writing it once inside a library you did not maintain is better than writing it once inside a shared hook you did. ## The React Hook Form mechanism worth naming React Hook Form's design point is that field values do not live in React state. `register('email')` returns the props (`name`, `onChange`, `onBlur`, `ref`) that wire an input into a store the library keeps outside the render cycle, and components subscribe only to the slices they read. The consequence is that typing in one field does not force a render of the whole form. `handleSubmit(onValid)` wraps your submit function — it prevents the default, runs validation and only calls you with the collected values if everything passes. `formState` exposes `errors`, `isDirty`, `isSubmitting` and the rest. Validation is usually delegated to a schema through a resolver, and `Controller` exists for components that must be controlled, which is how design-system inputs and third-party pickers get integrated. Formik occupies the other end: it keeps values in React state and re-renders the form as they change, with `validationSchema` for rules. That is simpler to reason about and completely fine for small forms; it is also why large Formik forms became a known performance topic. Be honest about the render claim, though. A hand-rolled form only has a render problem if it is big or its fields are expensive; a five-field login form re-rendering per keystroke costs nothing measurable. "Fewer re-renders" is a real benefit that applies to a real minority of forms. ## The decision, framed properly Adopting a library is a codebase-level decision, so weigh it at that level: **In favour.** Many forms, and growing. Validation rules that must be expressed once and reused — the same schema parsing on the client for feedback and on the server for enforcement is a strong argument, because duplicated rules drift silently. Dynamic field arrays, wizards, or forms assembled from configuration. A team whose forms currently disagree with each other about when errors appear: the library imposes one answer, and consistency across twenty forms is worth more than the ideal behaviour of any one of them. **Against.** A handful of small forms, where the library's setup is more code than the form. A design system whose inputs are all controlled, so most fields need `Controller` wrappers and you inherit the integration cost without the render benefit. A team unfamiliar with the library, since forms are exactly where junior engineers work and an unfamiliar abstraction gets misused. And the direction of travel: React 19's function-valued `action` prop plus its form and action hooks already cover simple submit paths, including pending state, without a dependency. **The middle option people forget.** Extract your own `useForm` hook. If what you actually need is values, touched, derived errors and a submit wrapper, that is well under a hundred lines, has zero integration cost with your components, and can be replaced by a library later if the requirements grow. Reaching for a dependency is not the only way to stop repeating yourself. ## Migration, if you adopt Do not rewrite the existing forms. Pick the next new complex form as the pilot, keep the old forms as they are until they need work anyway, and write down which one is the house pattern so the codebase does not settle into a permanent 50/50 split. Two coexisting patterns is fine as a transition state and expensive as a permanent one. ## What no library gives you It does not make the client the authority. Whatever schema runs in the browser, the server re-validates every submission, because the client is under the user's control. And it does not produce accessible forms by itself — the association between a message and its field, and how a change is announced, is still markup you are responsible for. ## The answer that lands Name the bookkeeping being outsourced, name the mechanism (values outside React state, subscription per field), give volume-and-shape criteria rather than a blanket preference, mention that a home-grown hook is the middle option, and finish on the server still being the authority.
- What is the concrete cost of adopting a form library in an app whose inputs all come from a controlled design system?Most fields cannot use the library's plain register path and need a controller wrapper instead, which means every design-system input gets an adapter layer. You pay the integration and learning cost, but you lose much of the render benefit, because those fields are controlled again. In that situation the honest case for adoption is validation and bookkeeping consistency, not performance.
- When would you write your own useForm hook instead of taking the dependency?When the requirement is values plus touched, derived errors and a submit wrapper — that is a small, readable hook with no integration cost against your existing components, and your team already understands every line of it. I would switch to a library once field arrays, cross-field schema validation, wizard state or focus management arrive, because that is where the home-grown version stops being small.
- How would you roll this out across a codebase that already has twenty hand-rolled forms?Pilot it on the next new complex form rather than migrating anything, and let the existing forms move only when they are being changed for other reasons. Write down which pattern is the house standard so the split is a transition rather than a permanent fork, and set a review date. A big-bang rewrite of working forms buys risk and no user-visible value.
saying these in an interview costs you the question
- Says a form library is always the right call
- Cannot name what it does beyond holding input values
- Claims it removes the need for server-side validation
- Assumes every form has a re-render problem worth solving
- Ignores the cost of wrapping controlled design-system inputs
- Proposes migrating every existing form at once