Формы в React почти всегда воспринимаются одинаково: как набор компонентов. Обычно используется стек:

  • React Hook Form — управление состоянием формы.
  • Zod — валидация данных.
  • React Query — отправка данных и синхронизация с сервером.

Для большинства задач это отлично работает: формы входа, настройки, CRUD‑интерфейсы.

Но иногда форма начинает превращаться во что‑то большее:

  • появляются правила видимости полей;
  • вычисляемые значения;
  • страницы, которые нужно пропускать;
  • логика, зависящая от предыдущих ответов.

В этот момент форма уже становится не просто UI, а движком принятия решений.

В статье мы рассмотрим два подхода:

  1. React Hook Form + Zod + React Query
  2. SurveyJS — форма как JSON‑схема

И построим одну и ту же форму двумя способами.


Форма, которую мы будем создавать

Multi-step dynamic form

Форма состоит из 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-query
  • survey-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

ЗадачаRHFSurveyJS
ВидимостьJSXvisibleIf
ВычисленияuseWatch / useMemoexpression
ВалидацияsuperRefineправила схемы
Навигацияstep stateстраницы
Логикараспределенацентрализована

React остаётся только для:

  • отображения
  • интеграции с API
  • стилизации

Когда использовать каждый подход

React Hook Form + Zod

Подходит если:

  • форма CRUD‑типа
  • логика простая
  • всё контролируется разработчиками

SurveyJS

Лучше когда:

  • форма содержит бизнес‑правила
  • логика часто меняется
  • её должны редактировать не разработчики
  • одну форму нужно запускать в разных приложениях

Итог

Эти подходы не конкурируют.

  • React Hook Form — для UI‑форм.
  • SurveyJS — для форм‑процессов с правилами.

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


Теги: react, nextjs, forms

Источник