Работа с Formik

Formik часто применяется как слой управления состоянием формы, а Yup выступает декларативным механизмом описания схемы валидации. Их связка позволяет отделить логику проверки данных от компонентов интерфейса и минимизировать количество императивного кода в обработчиках событий.

Основная модель взаимодействия строится вокруг передачи схемы Yup в параметр validationSchema у Formik. В этом случае Formik автоматически выполняет валидацию при изменении значений, потере фокуса или отправке формы, в зависимости от конфигурации.

Yup предоставляет цепочечный API для описания структуры данных формы. Каждое поле описывается через соответствующий тип:

  • string() — строковые значения
  • number() — числовые значения
  • boolean() — логические значения
  • date() — даты
  • array() — массивы
  • object() — вложенные структуры

Простейшая схема для формы регистрации может выглядеть следующим образом:

import * as Yup from "yup";

const schema = Yup.object({
  email: Yup.string()
    .email("Некорректный формат email")
    .required("Email обязателен"),

  password: Yup.string()
    .min(8, "Минимум 8 символов")
    .required("Пароль обязателен")
});

Каждое правило в цепочке добавляет ограничение, а строковые аргументы задают сообщение об ошибке.

Подключение схемы к Formik

Formik принимает схему через свойство validationSchema. После этого ручное описание функции validate становится избыточным.

import { Formik, Form, Field, ErrorMessage } from "formik";

<Formik
  initialValues={{ email: "", password: "" }}
  validationSchema={schema}
  onSub mit={(values) => {
    console.log(values);
  }}
>
  <Form>
    <Field name="email" />
    <ErrorMessage name="email" />

    <Field name="password" type="password" />
    <ErrorMessage name="password" />

    <button type="submit">Отправить</button>
  </Form>
</Formik>

Formik синхронизирует значения формы с Yup-схемой и формирует объект ошибок, который доступен через errors.

Структура ошибок и взаимодействие с Formik

Результат валидации Yup представляет собой объект, где ключи соответствуют полям формы. Formik использует его для отображения ошибок и управления состоянием touched.

Пример структуры:

{
  email: "Некорректный формат email",
  password: "Минимум 8 символов"
}

Ошибки отображаются только для полей, помеченных как touched, что предотвращает преждевременный вывод сообщений.

Базовые валидаторы Yup

Проверка строк

Yup.string()
  .required()
  .min(3)
  .max(20)
  .matches(/^[a-zA-Z]+$/)

Проверка чисел

Yup.number()
  .required()
  .min(0)
  .max(100)
  .integer()

Проверка email и URL

Yup.string().email()
Yup.string().url()

Условная валидация через when

Механизм when позволяет строить зависимости между полями. Это используется в сценариях, где одно поле влияет на правила другого.

Yup.object({
  password: Yup.string().required(),
  confirmPassword: Yup.string().when("password", (password, schema) => {
    return schema.oneOf([password], "Пароли должны совпадать");
  })
});

Более сложный пример с несколькими зависимостями:

Yup.string().when(["role", "isActive"], (role, isActive, schema) => {
  if (role === "admin" && isActive) {
    return schema.required("Обязательно для активного администратора");
  }
  return schema;
});

Вложенные объекты

Formik поддерживает вложенные структуры через точечную нотацию, а Yup описывает их через object.

const schema = Yup.object({
  user: Yup.object({
    name: Yup.string().required(),
    age: Yup.number().min(18)
  })
});

Доступ к значениям в Formik:

<Field name="user.name" />
<Field name="user.age" />

Работа с массивами

Для динамических списков используется Yup.array. Это особенно важно при использовании FieldArray в Formik.

const schema = Yup.object({
  tags: Yup.array()
    .of(Yup.string().required("Тег не может быть пустым"))
    .min(1, "Добавить минимум один тег")
});

Сложный вариант с объектами:

Yup.array().of(
  Yup.object({
    title: Yup.string().required(),
    quantity: Yup.number().min(1)
  })
);

Асинхронная валидация через test

Метод test позволяет реализовать пользовательские проверки, включая асинхронные операции, например запросы к API.

Yup.string().test(
  "check-username",
  "Имя уже занято",
  async (value) => {
    const response = await fetch(`/api/check?username=${value}`);
    const data = await response.json();
    return data.available;
  }
);

Асинхронная валидация требует осторожности: частые запросы могут создавать нагрузку, поэтому часто применяется debounce на уровне UI.

Кастомные правила валидации

Метод test также используется для синхронной логики:

Yup.number().test(
  "even-number",
  "Число должно быть чётным",
  (value) => value % 2 === 0
);

Можно создавать цепочки проверок с доступом к контексту:

Yup.string().test(
  "starts-with",
  function (value) {
    const prefix = this.options.context?.prefix;
    return value.startsWith(prefix);
  }
);

Преобразование значений (transform)

Yup позволяет преобразовывать входные данные до валидации.

Yup.number()
  .transform((value, originalValue) => {
    return String(originalValue).trim() === "" ? null : value;
  })
  .nullable();

Типичный кейс — обработка пустых строк как null или undefined.

Повторное использование схем

Схемы можно компоновать и переиспользовать, что снижает дублирование логики.

const baseString = Yup.string().trim().required();

const schema = Yup.object({
  firstName: baseString,
  lastName: baseString
});

Также возможна композиция через concat:

const a = Yup.string().min(3);
const b = Yup.string().max(10);

const combined = a.concat(b);

Типизация с TypeScript

Yup предоставляет вывод типов через InferType, что позволяет синхронизировать форму и типы данных.

import * as Yup from "yup";

const schema = Yup.object({
  email: Yup.string().email().required(),
  age: Yup.number().required()
});

type FormValues = Yup.InferType<typeof schema>;

Это уменьшает риск расхождения между схемой и типами в коде компонента.

Частые паттерны в формах

Подтверждение пароля

Yup.object({
  password: Yup.string().required(),
  confirmPassword: Yup.string().oneOf(
    [Yup.ref("password")],
    "Пароли не совпадают"
  )
});

Опциональные поля с зависимостью

Yup.object({
  hasCompany: Yup.boolean(),
  companyName: Yup.string().when("hasCompany", {
    is: true,
    then: (schema) => schema.required(),
    otherwise: (schema) => schema.notRequired()
  })
});

Особенности производительности

При больших формах с десятками полей схема Yup может становиться тяжёлой. Основные факторы влияния:

  • количество test-проверок
  • асинхронные валидаторы
  • вложенные структуры глубокой вложенности
  • частота пересборки схемы внутри компонента

Для оптимизации используется мемоизация схем и вынос за пределы компонента, чтобы избежать повторного создания объекта при каждом рендере.

Поведение при внешних изменениях данных

Formik пересчитывает валидацию при изменении initialValues и values. При интеграции с внешними источниками данных (API, глобальные состояния) важно учитывать:

  • пересоздание схемы может сбрасывать кэш ошибок
  • асинхронные проверки могут устаревать при быстром вводе
  • зависимости через when требуют стабильной структуры данных

Сложные формы и масштабирование схем

В крупных приложениях схема Yup часто разбивается на модули:

  • базовые схемы (string, number, email)
  • доменные схемы (user, billing, settings)
  • составные схемы форм

Такой подход снижает связность и упрощает сопровождение, особенно при повторном использовании логики в нескольких формах.

Formik и Yup в связке формируют декларативную модель валидации, где бизнес-правила отделены от интерфейсной логики, а структура данных формализована через единый источник истины.