Работа с формами – @mantine/form, хук useForm
В прошлом уроке мы разобрались со стилизацией компонентов. Теперь пришло время перейти к одной из самых мощных и полезных возможностей Mantine — работе с формами. Библиотека @mantine/form предоставляет хук useForm, который берет на себя всю рутину: управление состоянием полей, валидацию, обработку ошибок и отправку данных. И все это — без лишних зависимостей.
Установка и первый запуск
Прежде всего, установите пакет @mantine/form в ваш проект:
npm install @mantine/form
Этот пакет не зависит от @mantine/core и может использоваться с любыми React-компонентами ввода, но в связке с компонентами Mantine он работает особенно удобно.
Базовое использование: создаем простую форму
Давайте создадим форму регистрации, чтобы увидеть, как работает useForm на практике.
import { useForm } from '@mantine/form';
import { TextInput, Button, Box } from '@mantine/core';
interface FormValues {
name: string;
email: string;
age: number;
}
function RegistrationForm() {
const form = useForm<FormValues>({
mode: 'uncontrolled', // Рекомендуемый режим для производительности [citation:10]
initialValues: {
name: '',
email: '',
age: 0,
},
// Объект с правилами валидации
validate: {
name: (value) => (value.length < 2 ? 'Имя должно содержать минимум 2 буквы' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Некорректный email'),
age: (value) => (value < 18 ? 'Вам должно быть минимум 18 лет' : null),
},
});
return (
<Box component="form" onSubmit={form.onSubmit((values) => console.log(values))}>
<TextInput
label="Имя"
placeholder="Ваше имя"
withAsterisk
key={form.key('name')} // Обязательно для uncontrolled режима [citation:10]
{...form.getInputProps('name')}
/>
<TextInput
label="Email"
placeholder="your@email.com"
withAsterisk
mt="md"
key={form.key('email')}
{...form.getInputProps('email')}
/>
<TextInput
label="Возраст"
placeholder="Ваш возраст"
type="number"
mt="md"
key={form.key('age')}
{...form.getInputProps('age')}
/>
<Button type="submit" mt="md">
Зарегистрироваться
</Button>
</Box>
);
}
Разберем, что здесь происходит:
useForm<FormValues>— создаем экземпляр формы с типизацией.mode: 'uncontrolled'— рекомендуемый режим, в котором значения хранятся в DOM, что дает лучшую производительность.initialValues— начальные значения всех полей.validate— объект, где ключи совпадают с полями формы, а значения — функции валидации. Функция должна вернуть строку с ошибкой илиnull, если ошибок нет.form.onSubmit— обработчик отправки, который вызывается только после успешной валидации.form.getInputProps('name')— возвращает объект с пропсами (value,onChange,onBlur,errorи др.) для связывания поля с состоянием формы.form.key('name')— необходим для корректной работыuncontrolledрежима, гарантирует правильную перерисовку компонентов.
Валидация: от простого к сложному
1. Простые правила
В примере выше мы уже использовали простые функции для проверки каждого поля по отдельности. Это самый распространенный способ.
const form = useForm({
// ...
validate: {
name: (value) => (value.length < 2 ? 'Слишком короткое имя' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Неверный email'),
},
});
2. Валидация с учетом других полей (кросс-поля)
Функции валидации могут принимать вторым аргументом все значения формы, что позволяет проверять поля в связке друг с другом. Например, для проверки совпадения паролей:
const form = useForm({
// ...
initialValues: {
password: '',
confirmPassword: '',
},
validate: {
password: (value) => (value.length < 6 ? 'Пароль должен быть минимум 6 символов' : null),
confirmPassword: (value, values) =>
value !== values.password ? 'Пароли не совпадают' : null,
},
});
3. Валидация при вводе и потере фокуса
По умолчанию ошибка появляется только при попытке отправки формы. Чтобы валидировать поля в реальном времени, используйте опции validateInputOnChange и validateInputOnBlur.
const form = useForm({
// ...
validateInputOnChange: true, // Валидировать при каждом изменении
validateInputOnBlur: ['name', 'email'], // Валидировать при потере фокуса только для name и email
});
Можно также передать массив путей полей для более точного контроля.
4. Очистка ошибок
По умолчанию ошибка поля очищается, как только пользователь начинает изменять его значение. Это можно отключить с помощью clearInputErrorOnChange: false.
Схемная валидация (Zod)
Для сложных форм с большим количеством полей гораздо удобнее использовать декларативные схемы валидации. Mantine поддерживает Zod «из коробки» через новую утилиту schemaResolver.
Важное обновление для Mantine 9: начиная с этой версии, для Zod и других библиотек, совместимых со Standard Schema, больше не нужен отдельный пакет mantine-form-zod-resolver. Вместо него используется schemaResolver из @mantine/form.
Собери свой код. Запусти сайт!
От наброска на салфетке до первого работающего лендинга. Наш онлайн-курс «Веб-верстка с нуля и до профессионала» — это интенсивный трек, где ты не будешь зубрить теорию, а с первого дня начнешь превращать идеи в чистый HTML и CSS.
Собери свой первый проект под руководством практикующих разработчиков.
Установка Zod
npm install zod
Использование schemaResolver
import { useForm, schemaResolver } from '@mantine/form';
import { z } from 'zod';
// Определяем схему
const schema = z.object({
name: z.string().min(2, { message: 'Имя должно быть минимум 2 символа' }),
email: z.string().email({ message: 'Некорректный email' }),
age: z.number().min(18, { message: 'Вам должно быть минимум 18 лет' }),
});
function DemoForm() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
age: 0,
},
// Используем schemaResolver
validate: schemaResolver(schema, { sync: true }), // { sync: true } для синхронных схем [citation:9]
});
// ... остальной код формы
}
Почему это удобно?
- Декларативность: Все правила описаны в одном месте.
- Мощные проверки: Zod поддерживает сложные структуры, вложенные объекты, массивы и даже асинхронную валидацию.
- Читаемость: Код становится более понятным и легким в поддержке.
Управление состоянием
useForm предоставляет множество методов для управления данными формы:
const form = useForm({
// ...
initialValues: { name: '', email: '' },
});
// Получить все значения
const values = form.getValues();
// Установить значение конкретного поля
form.setFieldValue('email', 'new@email.com');
// Установить несколько значений
form.setValues({ name: 'John', email: 'john@email.com' });
// Сбросить форму к initialValues
form.reset();
// Сбросить только одно поле
form.resetField('email');
// Обновить initialValues (например, после загрузки данных)
form.setInitialValues({ name: 'John', email: 'john@example.com' });
Вложенные поля и массивы
useForm отлично справляется с работой со сложными структурами данных, такими как вложенные объекты и массивы объектов.
Вложенные объекты
Доступ к вложенным полям осуществляется через точечную нотацию 'user.firstName'.
const form = useForm({
// ...
initialValues: {
user: {
firstName: '',
lastName: '',
},
},
});
// В компоненте
<TextInput
key={form.key('user.firstName')}
{...form.getInputProps('user.firstName')}
/>
Массивы объектов
Для работы с массивами используйте индекс элемента: 'employees.0.name'.
const form = useForm({
// ...
initialValues: {
employees: [{ name: '', active: false }],
},
});
// Рендерим список сотрудников
const employees = form.getValues().employees.map((item, index) => (
<TextInput
key={form.key(`employees.${index}.name`)}
label={`Сотрудник ${index + 1}`}
{...form.getInputProps(`employees.${index}.name`)}
/>
));
Полный пример с формой регистрации
Давайте соберем все воедино и создадим полноценную форму регистрации с использованием всех изученных концепций: uncontrolled режим, валидация с Zod, вложенные поля и обработка ошибок при отправке.
import { useForm, schemaResolver } from '@mantine/form';
import { TextInput, Button, Box, Checkbox, Group } from '@mantine/core';
import { z } from 'zod';
// 1. Определяем схему Zod
const registrationSchema = z.object({
name: z.string().min(2, { message: 'Имя должно быть минимум 2 символа' }),
email: z.string().email({ message: 'Некорректный email' }),
password: z.string().min(6, { message: 'Пароль должен быть минимум 6 символов' }),
confirmPassword: z.string(),
address: z.object({
city: z.string().min(2, { message: 'Укажите город' }),
street: z.string().optional(),
}),
}).refine((data) => data.password === data.confirmPassword, {
message: 'Пароли не совпадают',
path: ['confirmPassword'], // Ошибка будет привязана к полю confirmPassword
});
type RegistrationValues = z.infer<typeof registrationSchema>;
function RegistrationForm() {
const form = useForm<RegistrationValues>({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
password: '',
confirmPassword: '',
address: {
city: '',
street: '',
},
},
validate: schemaResolver(registrationSchema, { sync: true }),
validateInputOnChange: ['name', 'email'], // Валидация на лету для важных полей
});
const handleSubmit = (values: RegistrationValues) => {
// Здесь отправляем данные на сервер
console.log('Form submitted:', values);
};
const handleError = (errors: typeof form.errors) => {
// Действия при ошибках валидации, например, показать уведомление
console.error('Validation errors:', errors);
};
return (
<Box component="form" onSubmit={form.onSubmit(handleSubmit, handleError)}>
<TextInput
label="Имя"
placeholder="Ваше имя"
withAsterisk
key={form.key('name')}
{...form.getInputProps('name')}
/>
<TextInput
label="Email"
placeholder="your@email.com"
withAsterisk
mt="md"
key={form.key('email')}
{...form.getInputProps('email')}
/>
<TextInput
label="Пароль"
placeholder="Придумайте пароль"
type="password"
withAsterisk
mt="md"
key={form.key('password')}
{...form.getInputProps('password')}
/>
<TextInput
label="Подтвердите пароль"
placeholder="Повторите пароль"
type="password"
withAsterisk
mt="md"
key={form.key('confirmPassword')}
{...form.getInputProps('confirmPassword')}
/>
<TextInput
label="Город"
placeholder="Ваш город"
mt="md"
key={form.key('address.city')}
{...form.getInputProps('address.city')}
/>
<TextInput
label="Улица"
placeholder="Ваша улица"
mt="md"
key={form.key('address.street')}
{...form.getInputProps('address.street')}
/>
<Group justify="flex-end" mt="md">
<Button type="submit">Зарегистрироваться</Button>
</Group>
</Box>
);
}
export default RegistrationForm;
Итоги
Сегодня вы освоили фундаментальную тему работы с формами в Mantine:
- Установка и подключение
@mantine/form - Базовое создание формы с
useForm - Валидация с помощью функций и схем Zod
- Управление состоянием полей
- Работа с вложенными полями и массивами
Что должно быть в вашем проекте после этого урока:
- Установлен пакет
@mantine/form - Вы создали форму с
uncontrolledрежимом - Настроена валидация для всех полей (функции или Zod)
- Используются
getInputPropsиform.key()для связки с полями ввода - При отправке данные выводятся в консоль
В следующем уроке мы углубимся в тему валидации, рассмотрим продвинутые сценарии — асинхронную валидацию, работу с массивами полей (динамическое добавление/удаление) и интеграцию с бэкендом.