Формы в React почти всегда воспринимаются одинаково: как набор компонентов. Обычно используется стек:
- React Hook Form — управление состоянием формы.
- Zod — валидация данных.
- React Query — отправка данных и синхронизация с сервером.
Для большинства задач это отлично работает: формы входа, настройки, CRUD‑интерфейсы.
Но иногда форма начинает превращаться во что‑то большее:
- появляются правила видимости полей;
- вычисляемые значения;
- страницы, которые нужно пропускать;
- логика, зависящая от предыдущих ответов.
В этот момент форма уже становится не просто UI, а движком принятия решений.
В статье мы рассмотрим два подхода:
- React Hook Form + Zod + React Query
- SurveyJS — форма как JSON‑схема
И построим одну и ту же форму двумя способами.
Форма, которую мы будем создавать

Форма состоит из 4 шагов.
Шаг 1 — Данные пользователя
- Имя (обязательно)
- Email (обязательно)
Шаг 2 — Заказ
- Цена
- Количество
- Налог
Вычисляемые значения:
- Subtotal
- Tax
- Total
Шаг 3 — Аккаунт и отзыв
Есть ли аккаунт?
Если да:
- username
- password
Также есть оценка удовлетворённости (1–5).
Если:
- ≥4 — спрашиваем «Что понравилось?»
- ≤2 — спрашиваем «Что можно улучшить?»
Шаг 4 — Review
Показывается только если
total >= 100
Часть 1 — Компонентный подход (React Hook Form + Zod)
Установка
npm install react-hook-form zod @hookform/resolvers @tanstack/react-queryСхема Zod
import { z } from "zod";
export const formSchema = z.object({
firstName: z.string().min(1, "Required"),
email: z.string().email("Invalid email"),
price: z.number().min(0),
quantity: z.number().min(1),
taxRate: z.number(),
hasAccount: z.enum(["Yes", "No"]),
username: z.string().optional(),
password: z.string().optional(),
satisfaction: z.number().min(1).max(5),
positiveFeedback: z.string().optional(),
improvementFeedback: z.string().optional(),
}).superRefine((data, ctx) => {
if (data.hasAccount === "Yes") {
if (!data.username) {
ctx.addIssue({
code: "custom",
path: ["username"],
message: "Required"
});
}
if (!data.password || data.password.length < 6) {
ctx.addIssue({
code: "custom",
path: ["password"],
message: "Min 6 characters"
});
}
}
if (data.satisfaction >= 4 && !data.positiveFeedback) {
ctx.addIssue({
code: "custom",
path: ["positiveFeedback"],
message: "Please share what you liked"
});
}
if (data.satisfaction <= 2 && !data.improvementFeedback) {
ctx.addIssue({
code: "custom",
path: ["improvementFeedback"],
message: "Please tell us what to improve"
});
}
});Здесь важно заметить:
usernameиpasswordобъявлены какoptional- но становятся обязательными через
superRefine
Это связано с тем, что Zod описывает структуру данных, а не условия логики формы.
Что происходит в компоненте
В компоненте React появляется:
useWatch— для отслеживания значенийuseMemo— для вычислений- условный JSX — для показа полей
useState— для навигации между шагами
Например вычисления:
const subtotal = price * quantity;
const tax = subtotal * taxRate;
const total = subtotal + tax;И условный рендеринг:
{satisfaction >= 4 && (
<textarea {...register("positiveFeedback")} />
)}Форма работает отлично.
Но логика оказывается размазана по всему компоненту:
- вычисления
- правила видимости
- навигация
- условия отправки
Чтобы понять правило показа страницы review, нужно проследить несколько участков кода.
Часть 2 — Подход на основе схемы (SurveyJS)
Установка
npm install survey-core survey-react-ui @tanstack/react-querysurvey-core— движок формsurvey-react-ui— React‑рендеринг
Главная идея:
Форма описывается JSON‑схемой.
Та же форма как данные
export const surveySchema = {
title: "Order Flow",
showProgressBar: "top",
pages: [
{
name: "details",
elements: [
{ type: "text", name: "firstName", isRequired: true },
{ type: "text", name: "email", inputType: "email", isRequired: true }
]
},
{
name: "order",
elements: [
{ type: "text", name: "price", inputType: "number", defaultValue: 0 },
{ type: "text", name: "quantity", inputType: "number", defaultValue: 1 },
{
type: "expression",
name: "subtotal",
expression: "{price} * {quantity}"
},
{
type: "expression",
name: "tax",
expression: "{subtotal} * {taxRate}"
},
{
type: "expression",
name: "total",
expression: "{subtotal} + {tax}"
}
]
},
{
name: "account",
elements: [
{
type: "radiogroup",
name: "hasAccount",
choices: ["Yes", "No"]
},
{
type: "text",
name: "username",
visibleIf: "{hasAccount} = 'Yes'",
isRequired: true
},
{
type: "text",
name: "password",
inputType: "password",
visibleIf: "{hasAccount} = 'Yes'",
isRequired: true
}
]
},
{
name: "review",
visibleIf: "{total} >= 100"
}
]
}Теперь вся логика находится в одном месте.
Что переместилось из React
| Задача | RHF | SurveyJS |
|---|---|---|
| Видимость | JSX | visibleIf |
| Вычисления | useWatch / useMemo | expression |
| Валидация | superRefine | правила схемы |
| Навигация | step state | страницы |
| Логика | распределена | централизована |
React остаётся только для:
- отображения
- интеграции с API
- стилизации
Когда использовать каждый подход
React Hook Form + Zod
Подходит если:
- форма CRUD‑типа
- логика простая
- всё контролируется разработчиками
SurveyJS
Лучше когда:
- форма содержит бизнес‑правила
- логика часто меняется
- её должны редактировать не разработчики
- одну форму нужно запускать в разных приложениях
Итог
Эти подходы не конкурируют.
- React Hook Form — для UI‑форм.
- SurveyJS — для форм‑процессов с правилами.
Главная ошибка — использовать компонентный подход там, где форма уже превратилась в движок принятия решений.
Теги: react, nextjs, forms