FormWizard
Headless многошаговый визард из @reformer/cdk. config — это пара колбэков
validateStep / validateAll (не схемы): каждый возвращает boolean | Promise<boolean>.
Кнопка «Далее» запускает валидацию текущего шага и блокируется, пока шаг невалиден.
Variants — готовые конфигурации компонента: один и тот же компонент с разными преднастроенными пропсами. Витрина состояний (напр. обычный Select и Select с расширенной информацией — два варианта).
Examples — приёмы в коде: как решить задачу. Напр. как разные Data Provider’ы работают с компонентом (рецепты использования).
defineSteps — конфиг по селектору шага
Обычный config привязывает правила к шагу по индексу массива:
validateStep: (step) => validateModel(model, STEP_SCHEMAS[step - 1]). Индекс
хрупкий: добавление или перестановка шага молча рассинхронизируют правила с
позицией, а шаг без правил по умолчанию считается «валидным» — тихая дыра.
defineSteps адресует правила по selector шага (тот же id, что у ноды шага в
схеме), а не по [step - 1]. Порядок ключей = порядок шагов; шаг без правил
объявляется явно через null. На выходе — обычный FormWizardConfig, визард
потребляет его как раньше.
import { FormWizard, defineSteps } from '@reformer/cdk/form-wizard';
// step1 / step2 / crossFieldRules — ValidationSchema<Root>
const config = defineSteps<'loan' | 'applicant' | 'confirm', Root>(model, {
steps: {
loan: step1,
applicant: step2,
confirm: null, // без правил — объявлено ЯВНО, а не по умолчанию
},
extras: crossFieldRules, // cross-field/warnings уровня формы — только на submit
});
<FormWizard form={form} config={config}>
{/* …Step / Actions… */}
</FormWizard>;
Результат — { validateStep, validateAll, stepSelectors, createStepController }.
validateStep(n) резолвит n → selector → правила внутри (хрупкая индексация
инкапсулирована здесь, приложение её больше не пишет); validateAll собирает все
непустые шаги плюс extras; stepSelectors — селекторы по порядку
(индекс = step - 1). Обе проверки идут с { touch: true } — метятся только
провалидированные поля, без сплошного form.markAsTouched().
createStepController относится к живому слою и разобран
ниже.
Выигрыш против STEP_SCHEMAS[step - 1]: перестановка шагов остаётся корректной
(правила следуют за селектором, а не за индексом); шаг без правил виден в конфиге
как null, а не как неявный пробел; вся арифметика смещений живёт в одном месте.
Живая валидация внутри шага — strategy
По умолчанию правила шага проверяются только на «Далее» (validateStep) и на
финальном submit (validateAll). defineSteps дополнительно принимает strategy
(плюс debounce и liveAfterSubmit) — live-стратегию внутри активного шага:
тот же Слой A, что и у createFormValidation/useFormValidation, но привязанный к
текущему шагу визарда.
const config = defineSteps<'loan' | 'confirm', Root>(model, {
steps: {
loan: step1,
confirm: null,
},
strategy: 'blur', // подсвечивать ошибки шага по потере фокуса
});
Значения strategy — 'submit' | 'blur' | 'change' | 'afterFirstSubmit' (та же
семантика, что у schema-стратегий Слоя A):
submit(default) — прогон только поvalidateStep/validateAll, живого слоя нет.blur— перепрогон при потере фокуса поля; раскрываются только сблюренные поля (поtouched).change— перепрогон на каждый ввод (учитываетdebounce); раскрываются только редактированные поля (поdirty).afterFirstSubmit— тихо до первого submit, дальше live поliveAfterSubmit('change'по умолчанию или'blur').
defineSteps также возвращает
createStepController(step): FormValidationController | null — контроллер
live-стратегии для конкретного шага (или null, если у шага нет правил либо
strategy не задана). Руками его обычно не дёргают: подписки поднимает хук ниже.
useWizardStepValidation — армит стратегию под текущий шаг
useWizardStepValidation(config) вызывается в теле компонента шага. Он берёт
currentStep из FormWizardContext, поднимает контроллер live-стратегии под этот
шаг и снимает подписки при смене шага или размонтировании — так живой слой всегда
следует за активным шагом.
import { useWizardStepValidation } from '@reformer/cdk/form-wizard';
function LoanStep() {
useWizardStepValidation(config); // config из defineSteps(..., { strategy: 'blur' })
return <>{/* поля шага */}</>;
}
Хук — no-op, если strategy не задана, равна 'submit', или у текущего шага
нет правил (объявлен null). Поэтому вызывать его безопасно в любом шаге.
Per-step gate («Далее» → validateStep) и submit (validateAll) не меняются —
strategy добавляет живой слой подсветки поверх них. Кнопка «Далее» по-прежнему
блокируется через validateStep, а финальный прогон идёт через validateAll с
{ touch: true }.