Хотите заказать веб-сайт? Связаться с нами

Работа с формами – @mantine/form, хук useForm

В прошлом уроке мы разобрались со стилизацией компонентов. Теперь пришло время перейти к одной из самых мощных и полезных возможностей Mantine — работе с формами. Библиотека @mantine/form предоставляет хук useForm, который берет на себя всю рутину: управление состоянием полей, валидацию, обработку ошибок и отправку данных. И все это — без лишних зависимостей.

Работа с формами – @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>
  );
}

Разберем, что здесь происходит:

  1. useForm<FormValues> — создаем экземпляр формы с типизацией.
  2. mode: 'uncontrolled' — рекомендуемый режим, в котором значения хранятся в DOM, что дает лучшую производительность.
  3. initialValues — начальные значения всех полей.
  4. validate — объект, где ключи совпадают с полями формы, а значения — функции валидации. Функция должна вернуть строку с ошибкой или null, если ошибок нет.
  5. form.onSubmit — обработчик отправки, который вызывается только после успешной валидации.
  6. form.getInputProps('name') — возвращает объект с пропсами (value, onChange, onBlur, error и др.) для связывания поля с состоянием формы.
  7. 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]
  });

  // ... остальной код формы
}

Почему это удобно?

  1. Декларативность: Все правила описаны в одном месте.
  2. Мощные проверки: Zod поддерживает сложные структуры, вложенные объекты, массивы и даже асинхронную валидацию.
  3. Читаемость: Код становится более понятным и легким в поддержке.

Управление состоянием

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:

  1. Установка и подключение @mantine/form
  2. Базовое создание формы с useForm
  3. Валидация с помощью функций и схем Zod
  4. Управление состоянием полей
  5. Работа с вложенными полями и массивами

Что должно быть в вашем проекте после этого урока:

  1. Установлен пакет @mantine/form
  2. Вы создали форму с uncontrolled режимом
  3. Настроена валидация для всех полей (функции или Zod)
  4. Используются getInputProps и form.key() для связки с полями ввода
  5. При отправке данные выводятся в консоль

В следующем уроке мы углубимся в тему валидации, рассмотрим продвинутые сценарии — асинхронную валидацию, работу с массивами полей (динамическое добавление/удаление) и интеграцию с бэкендом.

Теги: