# react-hook-form 7.89.0: an unanchored pattern, six build formats, and a v8 beta with no example

> react-hook-form is a forms library for React Hooks covering the web and React Native, MIT licensed, with no runtime dependencies of its own. The interesting details are in the artefacts rather than the pitch: the v8 line is a beta released four minutes after 7.89.0 while the examples stop at V7, the published package exposes six entry points including a react-server condition, and the quickstart's own age field validates with an unanchored regular expression.

**react-hook-form/react-hook-form** — GitHub describes it as 📋 React Hooks for form state management and validation (Web + React Native). The repository metadata lists TypeScript as its primary language. The metadata lists the MIT license. This article stays within the project description and details documented in the GitHub repository README.

- Repository: https://github.com/react-hook-form/react-hook-form
- Website: https://react-hook-form.com
- Stars: 44,868 · Forks: 2,479
- Language: TypeScript
- License: MIT
- Published: 2026-08-13 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/react-hook-form-react-hook-form

## v8.0.0-beta.4 shipped four minutes after 7.89.0, and the examples stop at V7

The install is one command:

```bash
npm install react-hook-form
```

The release list behind it has two entries from the same night. v7.89.0 was published at 2026-09-26T00:46:16Z and v8.0.0-beta.4 four minutes later at 2026-09-26T00:50:28Z, with v7.88.0 before that on 2026-09-11. The manifest in the repository is version 7.89.0, matching the stable line. Consequence: a plain install of react-hook-form resolves to the 7.x series, and the v8 line is something you opt into by naming a prerelease, so a team that reads the README and runs the install command is on 7 without noticing a major exists. The repository does not help you evaluate the beta: the examples directory contains V6/ and V7/ and nothing for v8, so a migration has no in-tree reference the way the earlier jumps did. Both platforms are still in scope on the stable line, since the package description covers web and React Native and the test script carries a TEST_ENV=web variant.

## The quickstart validates age with an unanchored pattern, so 12abc passes

The three registration calls in the minimal example are worth reading literally. There is register('firstName') with no options at all, and no error message rendered for it, so that field is unvalidated. There is register('lastName', { required: true }) with a rendered message. And there is register('age', { pattern: /\d+/ }) with the message Please enter a number for age. That regular expression is not anchored at either end, so it matches any string containing a digit, and 12abc or age 7 both satisfy it. Consequence: the example's own validation is weaker than its message implies, which is the kind of detail that gets copied into production. The rule vocabulary is also HTML-shaped rather than a general expression language, with values like required and pattern, so anything outside that small set means a custom validate function or a resolver package. And note the form element carries no noValidate attribute, so the browser's own validation runs alongside the library's and you can see two error surfaces at once.

## formState is nested and the example only ever reads errors from it

The destructuring at the top of the example is where the API shape is established. The result of useForm is pulled apart into register, handleSubmit, and formState, and formState is then destructured again to reach errors. So the minimal example establishes exactly one thing about form state, that errors lives under formState, and says nothing about the rest of that object's surface. Consequence: whether dirty state, touched state, submission flags, or a re-render counter are read the same way is something the quickstart does not answer, which is why the recurring questions about watch, setValue, trigger and isDirty are all about that one nested object. The inputs themselves are wired by spreading whatever register returns onto a plain input, with no value prop, so the DOM owns the value and the library reads it. That is the uncontrolled path, and it is the reason value handling questions come up as often as validation ones.

## The example's error message is not linked to the input it belongs to

Look at how an error is rendered. The input is registered and spread, and immediately after it comes a conditional paragraph containing the message, with no id on the input, no htmlFor on the paragraph, and no aria-invalid or aria-describedby anywhere. There is also no label element anywhere in the example. Consequence: what ships as the project's canonical minimal form is a rendering demonstration, not an accessible one, and anyone copying it inherits unlabelled text inputs and messages that assistive technology has no way to associate with the field they describe. This is not a criticism of the library, because exposing the error object is what the library can do, and the feature list's UX claim is about ergonomics rather than markup. It does mean the accessibility work is entirely on the integrator, and it is the one part of a form nobody can delegate to a package. The repository does carry a SECURITY.md and a CODE_OF_CONDUCT.md, but nothing in the tree that sets a markup standard for the examples.

## Six published entry points, a react-server condition, and a build that asserts its own exports

The manifest maps the same package six ways. main is dist/index.cjs.js, module and jsnext:main are dist/index.esm.mjs, umd:main plus unpkg and jsdelivr all point at dist/index.umd.js, types is dist/index.d.ts, and the exports map adds a react-server condition resolving to dist/react-server.esm.mjs. Consequence: a bare specifier can resolve to a different file depending on the module graph, so importing a hook in the wrong kind of module fails at resolution rather than at render, and a server component build will take the react-server branch. The build defends itself, because postbuild runs assert-esm-exports.mjs and then assert-cjs-exports.cjs, so a packaging mistake fails the build instead of reaching a consumer. One asymmetry is worth knowing: files contains only dist, while the source field points at src/index.ts, so the file the manifest names as the source is not shipped, and any tooling that tries to follow source to the original TypeScript will not find it in the installed package. sideEffects is false, which is the flag a bundler reads to drop unused imports.

## Jest and Playwright, with web and native selected by an environment variable

There are two test systems and they are not interchangeable. The unit suite is jest with its configuration at scripts/jest/jest.config.js, and the browser suite is playwright driven by playwright.config.ts at the root, with a ui variant for the interactive mode. Between them sits the platform split: test:web sets TEST_ENV=web and then runs the same jest command, so web and React Native behaviour is one configuration selected by an environment variable rather than two suites. Consequence: a platform difference is a configuration difference, which is cheap to run and easy to forget, so a fix verified on web has not been verified on native just because jest passed. Type checking is separate again, since test:type compiles src/__typetest__/tsconfig.json rather than running a test file. The strongest guarantee in the repository is api-extractor, run with a config at the root and writing into reports/, which means the exported type surface is machine compared and an unintended change to it becomes a build failure instead of a surprise in a consuming application.

## No dependencies is about the runtime tree, and the schema libraries are yours to add

The feature list pairs small size with no dependencies, and links the size claim to a bundle report and the dependency claim to the manifest. The next line names Yup, Zod, AJV, Superstruct, Joi and others, reached through a separate resolvers package. Those two statements are both true and they sit next to each other, so read them as a pair. Consequence: no dependencies describes what react-hook-form itself pulls in, not what your project ends up installing. The moment you want schema validation you add the schema library and a resolver, so the count goes from zero to at least two, and the resolver is a different package with its own version to track alongside yours. The rest of the tree is ordinary and worth knowing if you plan to contribute: a pnpm workspace with a lockfile, flat-config eslint, husky hooks wired through the prepare script, a codesandbox directory that backs the UI library integration example, and CHANGELOG.md as the record. The project is MIT licensed, funded by named sponsors and OpenCollective backers, with the documentation site backed by Vercel.

## Conclusion

react-hook-form fits a reader who wants uncontrolled inputs, a small runtime footprint, and schema validation through a separate resolvers package, and who is willing to write the accessibility markup the quickstart omits. It does not fit a reader who needs the v8 API today, since the beta has no example directory in the tree, or a reader who wants bundled schema validation, since every schema library is a package you add yourself. Before you adopt it, check five things: which line you are installing, because the manifest is 7.89.0 and the v8 beta is opt-in; whether your rules are HTML-shaped or schema-driven, since the built-in vocabulary is small and the resolver path is a second dependency; whether your form element sets noValidate, because the quickstart does not and the browser will validate alongside the library; whether the accessibility of your fields is your job, since the example renders errors as unlinked sibling paragraphs; and whether your bundler resolves the react-server condition, because a bare specifier can point at a different file in a server component graph.

## FAQ

### What is the React hook form?

It is a library for form state management and validation built on React Hooks, covering the web and React Native, MIT licensed and published on npm as react-hook-form. The quickstart calls one useForm hook and destructures its result into register, handleSubmit, and a nested formState object, then spreads what register returns onto plain input elements so the inputs stay uncontrolled.

### how to install react-hook-form

The README gives one command, npm install react-hook-form, and that resolves to the stable 7.x line. The manifest in the repository is version 7.89.0, published the same day as v8.0.0-beta.4, so a v8 install is a separate opt-in rather than the default.

### how to use react hook form with zod

Zod is one of the schema libraries the project names alongside Yup, AJV, Superstruct and Joi, and it is reached through a separate resolvers package rather than bundled, because the library declares no dependencies of its own. So zod and the resolver are both additions to your project, and the resolvers package carries its own version to track alongside react-hook-form.

### how to use react hook form controller

The feature list claims out of the box integration with UI libraries, and the link for that claim points at a CodeSandbox rather than at a section of the README, with a .codesandbox/ directory in the repository. The quickstart itself registers plain input elements and uses no controller, so the integration route is a separate example to read rather than something the minimal example demonstrates.

### how to use react hook form watch

The quickstart does not use watch. It destructures formState for errors and renders messages from that object, while each input is registered with no value prop, so the DOM holds the value and the library reads it. The example therefore establishes errors as part of formState and says nothing further about the rest of that object's surface, which is the documentation site rather than the README that covers it.

### how to reset react hook form

The README does not cover resetting. What the minimal example does establish is the shape of the API, one useForm call whose result is destructured into register, handleSubmit and a nested formState object, with validation expressed as register options such as { required: true } and a pattern option, and errors read off formState. The complete method list lives on the project's API documentation site.

## Sources

- [Official documentation](https://react-hook-form.com)
- [Official README](https://github.com/react-hook-form/react-hook-form#readme)
- [Project repository](https://github.com/react-hook-form/react-hook-form)
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/react-hook-form-react-hook-form
