React Hook Form 02 ๐ Installation โ Package setup & TypeScript requirements
React Hook Form’s installation is a single package with no runtime dependencies, but the TypeScript setup is where the configuration decides whether the form is type-safe or a source of any everywhere. The library is written in TypeScript, and the useForm hook is generic over the form values type. The generic is the mechanism that types the register names, the handleSubmit data, the formState.errors keys, and the Controller field values. A project that omits the generic gets the FieldValues default, which is Record<string, any>, and the type safety is lost at the boundary between the form and the application.
The installation surface has three layers. The core package is react-hook-form, and it is the only required dependency. The optional resolvers are @hookform/resolvers plus the schema libraryโZod, Yup, Valibotโand they are the bridge between the schema and the form. The dev tools are @hookform/devtools, and they are the debugging aid that renders the form state in a floating panel. The three layers are independent, and the project installs the ones it uses.
Key point: Install react-hook-form as the core dependency. The @hookform/resolvers package and the schema library are the optional resolver layer. The TypeScript setup requires the form values type as the generic on useForm, and the moduleResolution must be "bundler" or "node16"/"nodenext" for the resolver subpath exports to resolve.
Why the TypeScript setup is the installation decision
The generic type problem. The useForm hook is generic over the FieldValues type. The generic is what types the register names, the handleSubmit data, and the formState.errors. Without the generic, the library falls back to FieldValues, which is Record<string, any>, and the form is effectively untyped . The generic is the mechanism that makes the register("email") a typed field and the errors.email a typed error object.
The resolver version problem. The @hookform/resolvers package is the bridge between the schema library and the form. The Zod v4 support requires @hookform/resolvers ^3.10 or newer paired with zod ^3.25 or the Zod v4 . The version mismatch produces the Cannot find module '@hookform/resolvers/zod' error, which is the symptom of the missing subpath export, not the missing package. The version pairing is the installation constraint.
The module resolution problem. The @hookform/resolvers package uses the subpath exports, and the TypeScript moduleResolution must be "bundler" or "node16"/"nodenext" for the subpath to resolve . The older "node" resolution does not understand the exports field, and the import fails at the type level even when the runtime works. The tsconfig.json is the mechanism that makes the resolver import type-safe.
The trade-off. The TypeScript setup is the extra step, and the step is the reason the form is type-safe. The resolver layer is the optional dependency, and the dependency is the reason the schema validation works. The installation is small, and the configuration is the mechanism that makes it work.
a. The core installation and the generic type
The core package is react-hook-form, and the installation is a single command. The package has no runtime dependencies, and the bundle is roughly 12 KB .
# The core installation
npm install react-hook-form
The TypeScript setup is the generic on useForm. The form values type is the interface that describes the fields, and the generic is the connection between the type and the form.
// The form values type and the generic
import { useForm } from "react-hook-form";
type LoginFormValues = {
email: string;
password: string;
};
function LoginForm() {
// The generic is the form values type.
// It types the register names, the handleSubmit data, and the errors.
const { register, handleSubmit, formState: { errors } } = useForm<LoginFormValues>({
defaultValues: { email: "", password: "" },
});
return (
<form onSubmit={handleSubmit((data) => console.log(data.email))}>
<input {...register("email")} />
{/* The errors.email is typed. */}
{errors.email && <p>{errors.email.message}</p>}
</form>
);
}
The defaultValues option is the second part of the type setup. The option is typed against the generic, and a missing field is a compile error. The defaultValues is the mechanism that prevents the uncontrolled-to-controlled warning, and the type is the mechanism that catches the missing field.
// The defaultValues with the type checking
useForm<LoginFormValues>({
// The missing field is a compile error.
defaultValues: { email: "" },
});
// Property 'password' is missing in type '{ email: string; }'
The SubmitHandler type is the explicit form of the submit handler. The handleSubmit accepts the function, and the function receives the typed data. The SubmitHandler is the type that documents the intent, and the inline arrow function is the equivalent .
import { useForm, type SubmitHandler } from "react-hook-form";
const onSubmit: SubmitHandler<LoginFormValues> = (data) => {
// The data is typed.
console.log(data.email);
};
b. The resolver installation and the schema integration
The resolver layer is the optional dependency. The @hookform/resolvers package is the bridge, and the schema library is the validation. The Zod integration is the most common, and the installation is the resolver plus the schema.
# The Zod resolver installation
npm install @hookform/resolvers zod
The zodResolver is the function that connects the schema to the form. The useForm accepts the resolver option, and the resolver is the mechanism that runs the schema validation on the form data .
// The Zod resolver
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type LoginFormValues = z.infer<typeof schema>;
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm<LoginFormValues>({
// The resolver is the schema validation.
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
<input {...register("password")} type="password" />
{errors.password && <p>{errors.password.message}</p>}
</form>
);
}
The z.infer<typeof schema> is the type that the schema produces. The inferred type is the form values type, and the useForm generic is the inferred type. The two are the same type, and the schema is the single source of truth for the form shape and the validation .
The version pairing is the installation constraint. The Zod v4 support requires the @hookform/resolvers ^3.10 or newer, and the zod ^3.25 or the v4. The tsconfig.json requires the "moduleResolution": "bundler" or "node16"/"nodenext" for the subpath exports to resolve . The version mismatch is the cause of the Cannot find module error, not the missing package.
// tsconfig.json โ the module resolution for the resolvers
{
"compilerOptions": {
"moduleResolution": "bundler",
"strict": true
}
}
The strict flag is the second part of the TypeScript setup. The strict mode is the mechanism that catches the undefined fields, the any leaks, and the missing generics. The library is designed for the strict mode, and the strict flag is the recommended setting.
Complete Example Session
// ============================================
// PART 1: THE CORE INSTALLATION
// ============================================
// npm install react-hook-form
// The single package.
// No runtime dependencies.
// The bundle is roughly 12 KB.
// ============================================
// PART 2: THE FORM VALUES TYPE
// ============================================
type LoginFormValues = {
email: string;
password: string;
};
// The type is the form shape.
// It is the generic on the useForm.
// ============================================
// PART 3: THE USE FORM GENERIC
// ============================================
import { useForm } from "react-hook-form";
const { register, handleSubmit, formState: { errors } } = useForm<LoginFormValues>({
defaultValues: { email: "", password: "" },
});
// The generic types the register names.
// The generic types the handleSubmit data.
// The generic types the errors.
// ============================================
// PART 4: THE SUBMIT HANDLER TYPE
// ============================================
import { type SubmitHandler } from "react-hook-form";
const onSubmit: SubmitHandler<LoginFormValues> = (data) => {
console.log(data.email);
};
// The SubmitHandler is the explicit type.
// The inline arrow function is the equivalent.
// ============================================
// PART 5: THE ZOD RESOLVER INSTALLATION
// ============================================
// npm install @hookform/resolvers zod
// The resolver and the schema.
// The zodResolver connects the schema to the form.
// ============================================
// PART 6: THE SCHEMA AND THE INFERRED TYPE
// ============================================
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type SchemaValues = z.infer<typeof schema>;
// The inferred type is the form values.
// The schema is the single source of truth.
// ============================================
// PART 7: THE RESOLVER OPTION
// ============================================
import { zodResolver } from "@hookform/resolvers/zod";
const form = useForm<SchemaValues>({
resolver: zodResolver(schema),
});
// The resolver is the schema validation.
// The validation runs on the form data.
// ============================================
// PART 8: THE MODULE RESOLUTION
// ============================================
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler",
"strict": true
}
}
// The bundler resolution is for the subpath exports.
// The strict mode is for the type safety.
// ============================================
// PART 9: THE DEVTOOLS INSTALLATION
// ============================================
// npm install -D @hookform/devtools
import { DevTool } from "@hookform/devtools";
function Form() {
const { control } = useForm();
return (
<>
<form>{/* ... */}</form>
<DevTool control={control} />
</>
);
}
// The dev tools are the debugging aid.
// The control is the connection to the form.
// ============================================
// PART 10: THE CHECKLIST
// ============================================
// [ ] The react-hook-form is installed.
// [ ] The useForm has the generic.
// [ ] The defaultValues is typed.
// [ ] The @hookform/resolvers and the schema are installed for the resolver.
// [ ] The moduleResolution is "bundler" or "node16"/"nodenext".
// [ ] The strict mode is enabled.
// [ ] The @hookform/devtools is installed for the debugging.
The ten parts covered the core installation, the form values type, the useForm generic, the submit handler type, the Zod resolver installation, the schema and the inferred type, the resolver option, the module resolution, the dev tools installation, and the checklist. The through-line is that the generic is the type safety, and the resolver is the schema validation.
Quick Reference
The installation commands
| Package | Command | Purpose |
|---|---|---|
| Core | npm install react-hook-form | The form hook |
| Zod resolver | npm install @hookform/resolvers zod | The schema validation |
| Yup resolver | npm install @hookform/resolvers yup | The schema validation |
| Dev tools | npm install -D @hookform/devtools | The debugging aid |
The TypeScript requirements
| Requirement | Setting |
|---|---|
| Module resolution | "bundler" or "node16"/"nodenext" |
| Strict mode | "strict": true |
| The generic | useForm<FormValues>() |
| The inferred type | z.infer<typeof schema> |
The resolver versions
| Schema | Resolver version | Schema version |
|---|---|---|
| Zod v4 | @hookform/resolvers ^3.10+ | zod ^3.25 or v4 |
| Zod v3 | @hookform/resolvers ^3.x | zod ^3.x |
| Yup | @hookform/resolvers ^3.x | yup ^1.x |
Best Practices
โ Do This:
// Use the generic on useForm
useForm<LoginFormValues>() // โ
// Use the inferred type from the schema
type FormValues = z.infer<typeof schema>; // โ
// Set the moduleResolution to "bundler"
"moduleResolution": "bundler" // โ
โ Don’t Do This:
// Omit the generic
useForm() // the values are any // โ
// Use the "node" moduleResolution
"moduleResolution": "node" // the subpath exports fail // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
The Cannot find module '@hookform/resolvers/zod' | The version mismatch or the module resolution | Use the compatible versions and "bundler" |
The errors.email is any | The useForm has no generic | Add the generic |
The defaultValues is missing a field | The type is not enforced | The missing field is a compile error |
| The dev tools do not render | The control is not passed | Pass the control from useForm |
Real-World Examples
1. The core installation
npm install react-hook-form
2. The generic
useForm<LoginFormValues>()
3. The inferred type
type FormValues = z.infer<typeof schema>;
4. The Zod resolver
npm install @hookform/resolvers zod
5. The resolver option
resolver: zodResolver(schema)
6. The module resolution
{ "moduleResolution": "bundler" }
7. The submit handler type
const onSubmit: SubmitHandler<FormValues> = (data) => {};
8. The dev tools
npm install -D @hookform/devtools
9. The dev tools render
<DevTool control={control} />
10. The strict mode
{ "strict": true }
Visual
The Installation Layers
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE THREE LAYERS โ
โ โ
โ Core: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ react-hook-form โ The form hook โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโ โ
โ โ โ
โ Resolver (optional): โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ @hookform/resolvers โ The schema bridge โ
โ โ zod / yup โ The validation โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโ โ
โ โ โ
โ Dev tools (optional): โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ @hookform/devtools โ The debugging aid โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The TypeScript Setup
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE GENERIC AND THE RESOLVER โ
โ โ
โ type FormValues = { email: string } โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ useForm<FormValues> โ The generic types the form โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ register("email") โ The name is typed โ
โ โ errors.email โ The error is typed โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Version Pairing
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE ZOD VERSION PAIRING โ
โ โ
โ Zod v4: โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ @hookform/resolvers โ ^3.10 or newer โ
โ โ zod โ ^3.25 or v4 โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The version mismatch is the "Cannot find module" error. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Core package | react-hook-form |
| Resolver package | @hookform/resolvers |
| Dev tools | @hookform/devtools |
| TypeScript generic | useForm<FormValues>() |
| Inferred type | z.infer<typeof schema> |
| Module resolution | "bundler" or "node16"/"nodenext" |
| Strict mode | "strict": true |
Key takeaways:
- The core installation is a single package.
react-hook-formhas no runtime dependencies, and the bundle is roughly 12 KB. The library is written in TypeScript, and theuseFormhook is generic over the form values type . - The generic is the type safety. The
useForm<FormValues>()types theregisternames, thehandleSubmitdata, and theformState.errors. Without the generic, the form falls back toRecord<string, any>, and the type safety is lost . - The resolver layer is the optional schema validation. The
@hookform/resolverspackage and the schema libraryโZod, Yupโare the bridge between the schema and the form. ThezodResolverconnects the schema to theresolveroption . - The version pairing is the installation constraint. The Zod v4 support requires the
@hookform/resolvers^3.10 or newer, and thetsconfig.jsonrequires the"moduleResolution": "bundler"or"node16"/"nodenext"for the subpath exports to resolve . - The dev tools are the debugging aid. The
@hookform/devtoolspackage renders the form state in a floating panel, and thecontrolfromuseFormis the connection .
Remember: The React Hook Form installation is a single package, and the TypeScript setup is the generic on useForm. The generic types the form, and the resolver connects the schema. The version pairing and the module resolution are the constraints that make the resolver import type-safe. The core is small, and the configuration is the mechanism that makes it type-safe.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!