ПроКодинг - Откроем для вас мир IT!

Если ты только начинаешь работать с формами в React и чувствуешь, что каждый новый инпут - это как собирать паззл без картинки на коробке - ты не один. Верстка форм кажется простой: поле ввода, кнопка, стили. Но как только добавляешь валидацию, ошибки, асинхронные проверки и кастомные сообщения - всё рушится. И тут на помощь приходят React Hook Form и Yup. Вместе они делают формы не просто рабочими, а надёжными, чистыми и легко поддерживаемыми. Давай разберём, как это работает на практике, без лишней теории и сложных терминов.

Почему не просто useState и onChange?

Многие джуниоры начинают с простого: создают состояние для каждого поля через useState, ловят изменения через onChange, и вручную проверяют, например, что email содержит "@". Это работает - пока форм не становится больше трёх-четырёх. С каждым новым полем код растёт, становится запутанным, и ты начинаешь путать, где какое значение хранится. А если нужно проверить, что пароль совпадает с подтверждением? Или что поле не пустое только если выбран определённый чекбокс? Ты начинаешь писать костыли. И это нормально - все проходят через это. Но есть способ проще.

React Hook Form берёт на себя управление состоянием полей. Ты не пишешь onChange для каждого инпута. Ты просто даёшь ему register - и всё. Он сам отслеживает, что вводят, когда поле фокусируется, когда теряет фокус. Ты сосредотачиваешься на логике, а не на синтаксисе.

React Hook Form - как он работает

Представь, что у тебя есть форма для регистрации пользователя. Три поля: имя, email, пароль. С React Hook Form ты пишешь примерно так:

<form {...formMethods.handleSubmit(onSubmit)}>
  <input {...register('name')} />
  <input type="email" {...register('email')} />
  <input type="password" {...register('password')} />
  <button type="submit">Зарегистрироваться</button>
</form>

Всё. Никаких onChange, никаких value. React Hook Form сам связывает инпуты с данными. Когда пользователь что-то вводит - ты не пишешь код, чтобы обновить состояние. Форма делает это за тебя. А когда ты вызываешь handleSubmit, тебе приходит уже валидированный объект со всеми значениями. Это как если бы ты сказал: «Вот поля. Проверь их. Дай мне результат, когда всё ок».

Если нужно проверить, что поле обязательно - просто добавь required: true в register:

{...register('email', { required: 'Поле обязательно для заполнения' })}

Ошибка появится автоматически. Никаких ручных условий.

Yup - валидация как в законе

React Hook Form умеет валидировать, но только базово: «обязательно», «это email», «минимум 5 символов». А если нужно проверить, что пароль содержит хотя бы одну цифру и одну букву? Или что дата рождения не позже сегодняшнего дня? Тут на помощь приходит Yup.

Yup - это библиотека для описания схем валидации. Ты пишешь правила, как будто описываешь закон: «Если это email - он должен быть в формате [email protected]. Если это пароль - он должен быть не короче 8 символов и содержать цифру». Потом ты передаёшь эту схему в React Hook Form - и он сам применяет её.

Пример схемы для регистрации:

import * as yup from 'yup';

const schema = yup.object({
  name: yup.string().required('Имя обязательно'),
  email: yup.string().email('Неверный email').required('Поле обязательно'),
  password: yup.string()
    .min(8, 'Пароль должен быть не короче 8 символов')
    .matches(/[0-9]/, 'Должен содержать цифру')
    .matches(/[a-zA-Z]/, 'Должен содержать букву')
    .required('Поле обязательно'),
});

Здесь всё просто: ты описываешь, каким должен быть каждый элемент. Yup сам знает, что значит email(), min(), matches(). Никаких регулярных выражений вручную - только читаемый код.

А потом ты связываешь схему с формой:

const formMethods = useForm({
  resolver: yupResolver(schema),
});

И всё. Теперь при отправке формы Yup проверит каждое поле по твоим правилам. Если что-то не так - ошибки появятся рядом с полями. Если всё ок - ты получишь готовый объект с данными.

Форма валидируется: React Hook Form направляет ввод, а Yup проверяет правила — ошибки в красном, корректные данные в золотом.

Как выглядит форма в действии

Допустим, пользователь ввёл email test@ - без домена. Yup сразу говорит: «Неверный email». React Hook Form показывает это сообщение рядом с полем. Пользователь исправляет - сообщение исчезает. Ты не пишешь ни одной строки кода для скрытия ошибки. Это всё работает автоматически.

А если нужно показать ошибку только после того, как пользователь покинул поле (а не при каждом нажатии клавиши)? Просто добавь shouldValidateOnBlur: true в настройки формы. Или если хочешь, чтобы проверка шла только при отправке - тогда оставь как есть. Никаких костылей. Только настройки.

Кастомные ошибки и асинхронная валидация

А что, если нужно проверить, не занят ли email? Это уже асинхронно - нужно сделать запрос на сервер. Yup это тоже умеет. Добавь test в схему:

email: yup.string()
  .email()
  .required()
  .test('isUnique', 'Этот email уже занят', async (value) => {
    if (!value) return true;
    const response = await fetch('/api/check-email', {
      method: 'POST',
      body: JSON.stringify({ email: value }),
    });
    return response.ok;
  }),

Ты пишешь асинхронную функцию - Yup ждёт ответа. Если сервер говорит «занят» - появляется ошибка. Если свободен - всё ок. И всё это в той же схеме, что и остальные правила. Никаких отдельных состояний, никаких хуков для загрузки. Просто добавил тест - и всё работает.

Стили и UX - как сделать красиво

React Hook Form даёт тебе объект formState, в котором есть errors, isSubmitting, isValid. Их можно использовать для стилизации.

Например, если поле с ошибкой - подсвечиваем его красным:

<input
  {...register('email')}
  className={errors.email ? 'input-error' : 'input'}
/>

А кнопку отправки можно отключить, пока форма не валидна:

<button type="submit" disabled={!formMethods.formState.isValid}>
  Зарегистрироваться
</button>

Это не просто «красиво» - это улучшает UX. Пользователь не нажимает кнопку, пока не заполнит всё правильно. Никаких «ошибка при отправке» - только предупреждения прямо в форме.

Пользователь вводит email — появляется ошибка, кнопка отправки отключена, рядом визуализирована схема валидации Yup.

Что не стоит делать

  • Не смешивай React Hook Form с useState для тех же полей - это приведёт к конфликтам.
  • Не пиши валидацию вручную, если Yup может сделать это за тебя - ты будешь поддерживать код вечно.
  • Не используй useEffect для обработки ошибок - они уже есть в formState.errors.
  • Не отключай валидацию «на время разработки» - это потом будет сложно включить обратно.

Сравнение: Yup + React Hook Form vs ручная валидация

Сравнение подходов к валидации форм
Критерий React Hook Form + Yup Ручная валидация
Скорость разработки Очень высокая Низкая
Поддержка кода Простая Сложная
Асинхронные проверки Встроены Требуют костылей
Ошибки в UI Автоматически Вручную
Тестирование Простое Сложное

Что дальше?

Когда ты освоишь эту пару - попробуй добавить маски для телефонов (через react-input-mask), кастомные компоненты (например, выпадающий список), или валидацию дат (через date-fns или dayjs). Всё это интегрируется в ту же схему. Ты не переписываешь форму - просто расширяешь правила.

Верстка форм - это не про <input> и <label>. Это про то, как ты управляешь данными. React Hook Form и Yup - это инструменты, которые дают тебе контроль, а не усложняют жизнь. И когда ты начинаешь использовать их - ты перестаёшь думать о том, как «сделать», и начинаешь думать о том, «что делать».

Можно ли использовать Yup без React Hook Form?

Да, можно. Yup - это просто библиотека для валидации. Но без React Hook Form тебе придётся самому отслеживать значения полей, обновлять состояние, показывать ошибки. Это возможно, но теряется главное преимущество - автоматизация. Если ты не используешь React Hook Form, то Yup становится просто сложной альтернативой регулярным выражениям. Лучше использовать их вместе.

Почему не использовать Formik?

Formik - это тоже популярная библиотека, но он требует больше кода и работает иначе. React Hook Form легче, быстрее и не перерисовывает компоненты при каждом вводе. Formik часто приводит к лишним ререндерам, особенно на больших формах. React Hook Form работает с DOM напрямую - это эффективнее. Если ты начинаешь сейчас - выбирай React Hook Form. Formik устаревает.

Как проверить, что два поля совпадают (например, пароль и подтверждение)?

В Yup это делается через oneOf или test. Пример: yup.string().oneOf([yup.ref('password')], 'Пароли не совпадают'). Второе поле будет проверяться на соответствие первому. Если пользователь меняет пароль - ошибка исчезает автоматически. Никаких костылей.

Как обновить значение поля программно?

Используй setValue('fieldName', newValue) из объекта formMethods. Например, если ты загружаешь данные с сервера - ты не пишешь setState, а вызываешь setValue. Это обновит поле, сбросит ошибки и обновит валидацию. Это безопаснее, чем пытаться изменить DOM вручную.

Нужно ли учить TypeScript для работы с Yup и React Hook Form?

Не обязательно, но очень помогает. TypeScript позволяет точно описывать типы данных формы, и Yup с ним работает идеально. Ты получаешь автодополнение, проверку типов и меньше ошибок. Если ты используешь TypeScript - добавь тип для схемы: yup.object<FormValues>({...}). Это сделает код намного надёжнее.

Если ты только начинаешь - не бойся пробовать. Создай простую форму: имя, email, пароль. Подключи React Hook Form и Yup. Попробуй добавить одну кастомную проверку. Потом вторую. Потом асинхронную. Через пару дней ты перестанешь думать о том, как «сделать валидацию» - и начнёшь думать о том, как сделать форму удобнее для пользователя. Это и есть настоящая верстка.