React Hook Form 351,018 (github.com/react-hook-form/react-hook-form (https://github.com/react-hook-form/react-hook-form 44,864 ), MIT, npm 2,036 install react-hook-form) is the default choice today — version 7.88.0, September 2026, and roughly forty million npm downloads a week against Formik 34,320 's three million. Its trick is to keep field values in refs rather than in React 7,897 state, so typing re-renders nothing at all; only a validation error or a watched field causes a render. register('name') returns the ref, name, onChange and onBlur props to spread on an input.
import { useForm } from 'react-hook-form';
export default function Signup() {
const { register, handleSubmit, formState: { errors, isSubmitting } } =
useForm({ mode: 'onTouched' }); // validate on blur, then on change
const onSubmit = async (values) => console.log(values);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: 'Email is required' })}
aria-invalid={Boolean(errors.email)} />
{errors.email && <p role="alert">{errors.email.message}</p>}
<button disabled={isSubmitting}>Sign up</button>
</form>
);
}Swap the inline rules for a schema with @hookform/resolvers 5.9.1 — useForm({ resolver: zodResolver(schema) }) — and one Zod 44,027 object validates client and server. Components that hide their input, such as a date picker or a Material UI 55,831 field, cannot take a spread ref; wrap those in <Controller>. useFieldArray covers the repeating rows of Field Arrays and supplies a stable field.id for the React key.