Введение
Многошаговые формы часто используются, когда пользователю нужно заполнить длинную форму, но разработчики (как мы с вами) хотят улучшить его опыт. Разделив форму на несколько шагов, мы убираем ощущение перегрузки от множества полей. Каждому шагу даётся своё время и внимание, а мы можем валидировать данные поэтапно, чтобы минимизировать ошибки в дальнейшем.
Классический пример: форма регистрации на сайте, где на первом шаге заполняются личные данные, на втором — адрес, а на третьем — платёжная информация.
Структура многошаговой формы
Наша многошаговая форма будет состоять из двух основных частей:
- Навигация между шагами, где мы контролируем текущий шаг и управляем кнопками "Далее" и "Назад".
- Форма для данных текущего шага с отдельной схемой валидации для каждого этапа.
Для реализации мы будем использовать:
- Formik — для управления состоянием формы и её полями.
- Yup — для валидации данных.
- React — для управления логикой переключения шагов.
Создание структуры компонента
1. Создаём базовую структуру
Сначала мы построим скелет многошаговой формы. Здесь мы добавим логику для переходов между шагами:
import React, { useState } from 'react';
const MultiStepForm = () => {
// Текущее состояние шага
const [currentStep, setCurrentStep] = useState<number>(0);
// Данные шагов (для простоты — массив строк)
const steps = ['Личные данные', 'Адрес', 'Оплата'];
// Функция перехода на следующий шаг
const nextStep = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
}
};
// Функция возврата на предыдущий шаг
const prevStep = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
return (
<div>
<h1>{steps[currentStep]}</h1>
<div>
<button onClick={prevStep} disabled={currentStep === 0}>
Назад
</button>
<button onClick={nextStep} disabled={currentStep === steps.length - 1}>
Далее
</button>
</div>
</div>
);
};
export default MultiStepForm;
Сначала это базовый скелет. Форма отображает текущий шаг с кнопками "Назад" и "Далее". Управление осуществляется через состояние currentStep.
2. Добавляем формы для каждого шага
Теперь добавим формы для каждого шага. Создадим их как отдельные компоненты:
// Шаг 1: Форма для личных данных
const StepOne = () => (
<div>
<label>Имя</label>
<input type="text" name="name" />
<label>Email</label>
<input type="email" name="email" />
</div>
);
// Шаг 2: Форма для адреса
const StepTwo = () => (
<div>
<label>Город</label>
<input type="text" name="city" />
<label>Улица</label>
<input type="text" name="street" />
</div>
);
// Шаг 3: Форма для оплаты
const StepThree = () => (
<div>
<label>Номер карты</label>
<input type="text" name="cardNumber" />
<label>Дата окончания</label>
<input type="text" name="expiryDate" />
</div>
);
Теперь добавим их в наш компонент:
const renderStep = (step: number) => {
switch (step) {
case 0:
return <StepOne />;
case 1:
return <StepTwo />;
case 2:
return <StepThree />;
default:
return null;
}
};
const MultiStepForm = () => {
// Логика шагов остаётся прежней
return (
<div>
<h1>{steps[currentStep]}</h1>
{renderStep(currentStep)}
<div>
<button onClick={prevStep} disabled={currentStep === 0}>
Назад
</button>
<button onClick={nextStep} disabled={currentStep === steps.length - 1}>
Далее
</button>
</div>
</div>
);
};
Добавляем состояние формы с Formik
Теперь подключим Formik, чтобы управлять состоянием данных:
import { Formik, Form } from 'formik';
// Общий компонент с Formik
const MultiStepForm = () => {
const [currentStep, setCurrentStep] = useState<number>(0);
// Начальные данные всех шагов
const initialValues = {
name: '',
email: '',
city: '',
street: '',
cardNumber: '',
expiryDate: ''
};
return (
<Formik
initialValues={initialValues}
onSubmit={(values) => {
if (currentStep === steps.length - 1) {
console.log('Форма отправлена:', values);
} else {
nextStep();
}
}}
>
{({ values }) => (
<Form>
<h1>{steps[currentStep]}</h1>
{renderStep(currentStep)}
<div>
<button onClick={prevStep} disabled={currentStep === 0}>
Назад
</button>
<button type="submit">
{currentStep === steps.length - 1 ? 'Отправить' : 'Далее'}
</button>
</div>
</Form>
)}
</Formik>
);
};
Теперь данные сохранены в состоянии Formik, и можно их использовать.
Добавляем валидацию с Yup
Каждому шагу будет соответствовать своя схема валидации. Вот пример:
import * as Yup from 'yup';
// Схемы валидации для каждого шага
const StepOneSchema = Yup.object().shape({
name: Yup.string().required('Имя обязательно'),
email: Yup.string().email('Некорректный email').required('Email обязателен')
});
const StepTwoSchema = Yup.object().shape({
city: Yup.string().required('Город обязателен'),
street: Yup.string().required('Улица обязательна')
});
const StepThreeSchema = Yup.object().shape({
cardNumber: Yup.string()
.matches(/^\d+$/, 'Номер карты должен содержать только цифры')
.required('Номер карты обязателен'),
expiryDate: Yup.string().required('Дата окончания обязательна')
});
const validationSchema = (step: number) => {
switch (step) {
case 0:
return StepOneSchema;
case 1:
return StepTwoSchema;
case 2:
return StepThreeSchema;
default:
return null;
}
};
Обновим Formik, чтобы использовать validationSchema:
<Formik
initialValues={initialValues}
validationSchema={validationSchema(currentStep)}
onSubmit={(values) => {
if (currentStep === steps.length - 1) {
console.log('Форма отправлена:', values);
} else {
nextStep();
}
}}
>
Теперь каждый шаг валидируется своей схемой.
Сохраняем состояние между шагами
Formik уже автоматически сохраняет состояние формы, так что данные с предыдущих шагов не теряются. Но если вы хотите, чтобы они рендерились, можно в дальнейшем отображать значения в консоли или полях по умолчанию.
Типичные ошибки
Очень важно помнить, что при переключении шагов нужно быть аккуратным с валидацией. Например, если вы пытаетесь использовать данные из других шагов, но не проверяете их корректность, это может привести к неожиданным ошибкам. Также не забудьте правильно настроить зависимости всех функций, чтобы избежать лишних вызовов.
Это может быть ещё одной причиной, чтобы использовать такие библиотеки, как Formik, чтобы избежать ошибок, связанных с ручным управлением состоянием.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ