AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 14 — Forms, Types, and Validation — React Hook Form, Formik, TypeScript, and Zod

14 — Forms, Types, and Validation — React Hook Form, Formik, TypeScript, and Zod

August 13, 20267 min read
Download as Markdown

Forms and types converge on one question — how does data stay correct from a user's keystrokes to my app's logic? The split that organized it: a form library manages field state and submission, TypeScript catches mistakes during development, and Zod validates anything that crosses a trust boundary at runtime. [1][2][3][4] These are three different jobs at three different times; conflating them is where most form-and-validation confusion comes from.

Why a form library at all

A form sounds simple — some inputs and a submit button. In practice, building it in vanilla React means managing state for every field, tracking touched/dirty flags, computing validation errors, syncing errors back to the UI, and handling submission. Each piece is small; the aggregate is a wall of boilerplate, which is why the roadmap points at form libraries [1].

React Hook Form: the performant default

React Hook Form (RHF) is the dominant choice for new React work [2]. Its defining trait is performance through uncontrolled components — instead of re-rendering the whole form on every keystroke by holding each field in React state, RHF registers inputs via refs and reads their values at submit time. The practical effect: large forms don't slow down, because typing in one field doesn't re-render the others.

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

return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('email', { required: true })} />
{errors.email && <span>required</span>}
<button type="submit">Save</button>
</form>
);

RHF pairs cleanly with a schema validator (most commonly Zod, via @hookform/resolvers/zod) so the validation rules live in one schema, not scattered across register calls.

Formik: the older, controlled alternative

Formik is the library that defined this category [3]. Its model is controlled — form values live in React state, which makes every keystroke trigger a re-render. That's simpler to reason about than RHF's ref-based approach, but it's why Formik struggles on large forms. Formik is still a solid, well-understood library with great Yup integration; for new work I default to React Hook Form for the performance, but Formik is what I read in older codebases.

TypeScript: compile-time safety, not runtime validation

The roadmap pairs TypeScript with validation under "Types and Validation," and the critical distinction is when each operates [4]. TypeScript is a static type system that catches mistakes during development — wrong prop types, missing fields, typos in property names surface as red squiggles in the editor before I ever run the code. It's a developer-experience and correctness layer, and it's become the default for any serious React project.

The thing TypeScript explicitly does not do is validate data at runtime. A user: User annotation is a compile-time claim; if fetch('/api/user') returns { name: 42 } at runtime, TypeScript can't help, because it isn't running. Any data that crosses a trust boundary — a network response, a localStorage read, user form input — needs runtime validation, which is a different tool.

Zod: runtime validation at the boundaries

Zod is a TypeScript-first schema declaration and validation library, and it's the standard answer to the runtime-validation gap [5][6]. I declare a schema once; Zod both validates data against it at runtime and infers the matching TypeScript type, so I never write the type twice.

const UserSchema = z.object({
email: z.string().email(),
age: z.number().int().positive(),
});
type User = z.infer<typeof UserSchema>; // the type is derived from the schema

const result = UserSchema.safeParse(unknownData);
if (!result.success) {
// result.error lists every field that failed
} else {
// result.data is typed as User — safe to use
}

The model that clicked: Zod guards trust boundaries. [5][6] Anything that arrives from outside my code — an API response, form input, a URL param, a localStorage value — is unknown until I've validated it. TypeScript is fine for data my own code constructed (because the type system tracked it the whole way), but for anything external, the parse-then-use pattern is what keeps runtime bugs out.

Form library React Hook Form · Formik field state touched / dirty submit handling error display during user interaction TypeScript static type system wrong prop types missing fields property typos caught during development not at runtime Zod runtime validation API responses form input localStorage reads URL params at trust boundaries schema → inferred type three jobs at three times — state during use, types at write-time, validation at the boundary

How they compose: a real form

The three tools stack. A typical React form for me: TypeScript throughout for editor safety, React Hook Form for field state and submission, a Zod schema for validation, and the @hookform/resolvers/zod adapter wiring the schema into RHF. On submit, the same Zod schema runs; when the API responds, another Zod schema validates the response before I treat it as typed. The schema is the single source of truth — TypeScript types are inferred from it, so the type and the validator can't drift.

How I use this

For any new React project, TypeScript is non-negotiable — it's the baseline for developer-experience correctness on anything beyond a throwaway. For forms, React Hook Form is my default, with a Zod schema owning the validation rules and the form resolvers adapter gluing them together. Formik I maintain in legacy code but don't start new forms with. The mental discipline that ties it together: I ask where each piece of data came from. If my own code constructed it, TypeScript tracked it and that's enough. If it came from outside — a network call, user input, storage — it's unknown until Zod has parsed it, and only then does it become a typed value I can use. That single rule keeps both the editor happy and the runtime honest.

References

[1] R. Wieruch, "How to use forms in React," robinwieruch.de, 2023. [Online]. Available: https://www.robinwieruch.de/react-form/

[2] React Hook Form, "React Hook Form — performant, flexible and extensible forms," react-hook-form.com, 2024. [Online]. Available: https://react-hook-form.com/

[3] Formik, "Formik — build forms in React," formik.org, 2024. [Online]. Available: https://formik.org/

[4] Microsoft, "TypeScript — the language," typescriptlang.org, 2024. [Online]. Available: https://www.typescriptlang.org/

[5] Zod, "Zod — TypeScript-first schema validation," zod.dev, 2024. [Online]. Available: https://zod.dev/

[6] M. Hacks, "When should you use Zod?," totaltypescript.com, 2023. [Online]. Available: https://www.totaltypescript.com/when-should-you-use-zod

Knowledge check · Question 1 of 5

React Hook Form's performance advantage over Formik comes from:

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!