Современные формы в веб-приложениях требуют сочетания управляемого состояния, декларативной валидации и строгой типизации. Комбинация Formik и Zod формирует устойчивую архитектуру, в которой ответственность разделяется между управлением формой и описанием правил данных.
Formik обеспечивает жизненный цикл формы: хранение значений, отслеживание touched/dirty состояний, обработку сабмита и централизованное управление ошибками. Zod выступает как слой валидации и типового контракта, задающий структуру данных независимо от UI-логики.
Formik строится вокруг концепции контролируемых форм. Состояние формы хранится в едином объекте, а изменения происходят через явные обработчики.
Ключевые возможности:
values)errors)touched)handleSubmit)Базовая модель Formik выглядит как слой, абстрагирующий работу с input-элементами:
const formik = useFormik({
initialValues: {
email: '',
password: ''
},
onSubmit: (values) => {
console.log(values);
}
});
Formik не накладывает строгих правил на структуру данных, поэтому валидация выносится во внешние схемы.
Zod используется как декларативный инструмент описания формы и проверки входных данных. В отличие от ручных валидаторов, схема становится источником истины для структуры объекта.
Пример базовой схемы:
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
Сильная сторона Zod заключается в том, что схема одновременно:
Formik ожидает функцию валидации или schema-адаптер (например, Yup). Zod не интегрирован напрямую, поэтому создаётся промежуточный слой преобразования.
Типовой подход — функция адаптации:
const validateWithZod = (schema) => (values) => {
const result = schema.safeParse(values);
if (result.success) return {};
const errors = {};
result.error.issues.forEach((issue) => {
errors[issue.path[0]] = issue.message;
});
return errors;
};
Использование в Formik:
const formik = useFormik({
initialValues: {
email: '',
password: ''
},
validate: validateWithZod(schema),
onSubmit: (values) => {
console.log(values);
}
});
Такой подход сохраняет независимость Formik от Zod и обеспечивает явное преобразование ошибок.
При увеличении количества полей схемы Zod становятся вложенными и композиционными.
Пример вложенной структуры:
const profileSchema = z.object({
name: z.string().min(2),
contacts: z.object({
email: z.string().email(),
phone: z.string().optional()
})
});
Formik в этом случае хранит значения в аналогичной структуре:
initialValues: {
name: '',
contacts: {
email: '',
phone: ''
}
}
Ключевым моментом становится корректное отображение путей ошибок.
Zod возвращает ошибки в виде массива issues, где каждый
элемент содержит путь (path) и сообщение.
Для корректной интеграции требуется рекурсивная сборка объекта ошибок:
const buildErrors = (issues) => {
return issues.reduce((acc, issue) => {
const path = issue.path.join('.');
acc[path] = issue.message;
return acc;
}, {});
};
Это позволяет поддерживать вложенные поля Formik:
contacts.emailcontacts.phoneFormik автоматически сопоставляет такие ключи с соответствующими полями.
Одним из ключевых преимуществ Zod является генерация типов:
type FormValues = z.infer<typeof schema>;
Использование в Formik:
const formik = useFormik<FormValues>({
initialValues: {
email: '',
password: ''
},
validate: validateWithZod(schema),
onSubmit: (values) => {}
});
Это устраняет дублирование типов и схем.
Formik поддерживает асинхронную валидацию через validate
или validationSchema. В связке с Zod асинхронность чаще
реализуется отдельно от схемы.
Сценарий серверной проверки:
const submitHandler = async (values, { setErrors }) => {
const response = await api.check(values);
if (!response.ok) {
setErrors(response.errors);
return;
}
};
Zod используется только для синхронной структуры, а серверная логика дополняет её.
Zod поддерживает композицию через merge,
extend и partial.
Пример расширения:
const baseSchema = z.object({
email: z.string().email()
});
const extendedSchema = baseSchema.extend({
password: z.string().min(8)
});
Это позволяет использовать единые базовые контракты для нескольких форм.
Formik при этом остаётся неизменным, так как работает только с итоговой схемой.
Zod поддерживает условные правила через refine:
const schema = z.object({
password: z.string(),
confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
message: "Пароли не совпадают",
path: ["confirmPassword"]
});
Formik получает ошибку как часть стандартного механизма и отображает
её в поле confirmPassword.
Formik может вызывать частые обновления при изменении значений. При использовании Zod важно учитывать стоимость валидации.
Подходы оптимизации:
Пример отложенной валидации:
const debouncedValidate = debounce((values) => {
return validateWithZod(schema)(values);
}, 300);
Архитектурно связка Formik + Zod формирует три уровня:
Такое разделение позволяет:
Zod-схема часто становится контрактом между интерфейсом и сервером.
Пример:
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
Эта схема используется:
Formik поддерживает FieldArray, а Zod описывает массивы
через z.array.
const schema = z.object({
tags: z.array(z.string().min(1))
});
Formik:
pushremoveОшибки Zod приходят с индексами:
tags[0]tags[1]И требуют нормализации для Formik.
Обобщённая структура интеграции выглядит следующим образом:
Такое разделение снижает связность компонентов и повышает предсказуемость поведения форм в сложных приложениях.