webroad.online
  1. 1Веб
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Инструменты
  8. 8React
  9. 9Стейт-менеджмент
  10. 10Next.js
  11. 11Формы
  12. 12Данные и бэкенд
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Анимации
  16. 16Тестирование
  17. 17Архитектура
Формы · Урок 2 из 4

React Hook Form

useForm, register, handleSubmit, formState, Controller, useFieldArray, режимы валидации.

Обновлено

Что такое React Hook Form

React Hook Form (RHF) — самая популярная библиотека форм для React. Один хук, useForm, даёт всё нужное: регистрацию полей, валидацию, ошибки, состояние отправки.

Секрет производительности: она построена на неконтролируемых полях. Значения живут в DOM, а не в состоянии React → ввод не перерисовывает всю форму. На 50 полях разница ощутима.

'use client'
import { useForm } from 'react-hook-form'

type Values = { email: string; password: string }

export function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<Values>({ defaultValues: { email: '', password: '' } })

  const onSubmit = async (values: Values) => {
    await login(values)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <input type="email" {...register('email', { required: 'Email обязателен' })} aria-invalid={!!errors.email} />
      {errors.email && <p role="alert">{errors.email.message}</p>}

      <input type="password" {...register('password', { minLength: { value: 8, message: 'Минимум 8 символов' } })} />
      {errors.password && <p role="alert">{errors.password.message}</p>}

      <button disabled={isSubmitting}>Войти</button>
    </form>
  )
}

Части useForm

Часть Что делает
register('name', rules) подключает поле: возвращает name, ref, onChange, onBlur
handleSubmit(fn) валидирует, затем вызывает fn(values), только если всё верно
formState.errors ошибки по полям: errors.email?.message
formState.isSubmitting true, пока выполняется onSubmit (async)
formState.isDirty / dirtyFields что изменилось относительно defaultValues
formState.isValid форма сейчас валидна
watch('name') подписка на значение — перерисовка при изменении (используйте умеренно)
setValue, reset, setError управление из кода: заполнить, сбросить, ошибка с сервера

Когда проверять — режим

useForm({ mode: 'onTouched' })
mode Проверяет Ощущение
onSubmit (по умолчанию) при отправке, затем при каждом изменении ошибки только после первой отправки
onBlur при уходе с поля —
onTouched после первого blur, затем при каждом изменении самый дружелюбный — не ругается, пока вы печатаете
onChange при каждом нажатии агрессивно, дорого

Компоненты, которые не являются нативными полями: Controller

register работает с <input>, <select>, <textarea>. Для своих компонентов (выбор даты, select из UI-библиотеки) используют Controller, который превращает их в контролируемые поля:

<Controller
  control={control}
  name="country"
  render={({ field, fieldState }) => <CountrySelect {...field} error={fieldState.error?.message} />}
/>

Динамические списки: useFieldArray

const { fields, append, remove } = useFieldArray({ control, name: 'phones' })

{fields.map((field, i) => (
  <div key={field.id}>                                   {/* field.id, а не индекс */}
    <input {...register(`phones.${i}.number`)} />
    <button type="button" onClick={() => remove(i)}>Удалить</button>
  </div>
))}
<button type="button" onClick={() => append({ number: '' })}>Добавить телефон</button>

Валидация через схему

Правила, написанные в register, разбросаны по JSX. Рекомендуемый вариант: схема Zod + zodResolver — типы выводятся из схемы, а та же схема валидирует и на сервере. Подробнее — в уроке про Zod.

const form = useForm({ resolver: zodResolver(loginSchema), mode: 'onTouched' })

Частые ошибки

  • watch() без аргумента в главном компоненте → перерисовка всей формы при каждом нажатии. Используйте useWatch({ name }) в маленьком компоненте.
  • key={index} в useFieldArray — используйте field.id.
  • Валидация только в RHF, без проверки на сервере.

Коротко

  • useForm → register на полях, handleSubmit(onSubmit), formState.errors.
  • Внутри — неконтролируемые поля → быстро; Controller для своих компонентов, useFieldArray для списков.
  • mode: 'onTouched' + схема Zod через zodResolver.

Официальные источники

Упражнения

Была ли страница полезной?

Один клик — без регистрации.