| | |

React Hook Form 02 ๐Ÿ“‹ Installation โ€” Package setup & TypeScript requirements

โฑ๏ธ Estimated read: 11 min | |

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

PackageCommandPurpose
Corenpm install react-hook-formThe form hook
Zod resolvernpm install @hookform/resolvers zodThe schema validation
Yup resolvernpm install @hookform/resolvers yupThe schema validation
Dev toolsnpm install -D @hookform/devtoolsThe debugging aid

The TypeScript requirements

RequirementSetting
Module resolution"bundler" or "node16"/"nodenext"
Strict mode"strict": true
The genericuseForm<FormValues>()
The inferred typez.infer<typeof schema>

The resolver versions

SchemaResolver versionSchema version
Zod v4@hookform/resolvers ^3.10+zod ^3.25 or v4
Zod v3@hookform/resolvers ^3.xzod ^3.x
Yup@hookform/resolvers ^3.xyup ^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

PitfallWhy It HappensFix
The Cannot find module '@hookform/resolvers/zod'The version mismatch or the module resolutionUse the compatible versions and "bundler"
The errors.email is anyThe useForm has no genericAdd the generic
The defaultValues is missing a fieldThe type is not enforcedThe missing field is a compile error
The dev tools do not renderThe control is not passedPass 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

ItemValue
Core packagereact-hook-form
Resolver package@hookform/resolvers
Dev tools@hookform/devtools
TypeScript genericuseForm<FormValues>()
Inferred typez.infer<typeof schema>
Module resolution"bundler" or "node16"/"nodenext"
Strict mode"strict": true

Key takeaways:

  • The core installation is a single package. react-hook-form has no runtime dependencies, and the bundle is roughly 12 KB. The library is written in TypeScript, and the useForm hook is generic over the form values type .
  • The generic is the type safety. The useForm<FormValues>() types the register names, the handleSubmit data, and the formState.errors. Without the generic, the form falls back to Record<string, any>, and the type safety is lost .
  • The resolver layer is the optional schema validation. The @hookform/resolvers package and the schema libraryโ€”Zod, Yupโ€”are the bridge between the schema and the form. The zodResolver connects the schema to the resolver option .
  • The version pairing is the installation constraint. The Zod v4 support requires the @hookform/resolvers ^3.10 or newer, and the tsconfig.json requires the "moduleResolution": "bundler" or "node16"/"nodenext" for the subpath exports to resolve .
  • The dev tools are the debugging aid. The @hookform/devtools package renders the form state in a floating panel, and the control from useForm is 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.

Was this helpful?


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!