Валидация пользовательского ввода является критически важной частью интерфейсов. Она обеспечивает корректность данных, предотвращает ошибки на стороне сервера и повышает удобство взаимодействия. При использовании компонентов Radix UI валидация строится вокруг нативных возможностей HTML-форм, возможностей React и гибкой композиционной архитектуры самой библиотеки.
Компоненты Radix UI не содержат встроенной бизнес-логики проверки данных. Их задача — предоставить доступные, управляемые и расширяемые примитивы интерфейса, поверх которых реализуется логика валидации.
Основные принципы:
Благодаря этому Radix UI одинаково хорошо работает с различными стратегиями проверки данных.
В современных интерфейсах используются несколько типов проверки данных.
Основана на стандартных атрибутах элементов формы:
requiredminmaxpatterntype="email"type="number"Преимущества:
Недостатки:
Radix UI полностью совместим с этим подходом, поскольку использует настоящие HTML-элементы внутри своих примитивов.
Реализуется на уровне JavaScript/React.
Используется для:
Часто применяется для:
Radix UI предоставляет набор примитивов для построения форм через пакет:
@radix-ui/react-form
Этот пакет решает несколько задач:
Основные элементы:
| Компонент | Назначение |
|---|---|
Form.Root |
контейнер формы |
Form.Field |
логическое поле |
Form.Label |
подпись |
Form.Control |
элемент ввода |
Form.Message |
сообщение об ошибке |
Form.ValidityState |
состояние валидности |
Пример формы с валидацией.
import * as Form from "@radix-ui/react-form";
function LoginForm() {
return (
<Form.Root>
<Form.Field name="email">
<Form.Label>Email</Form.Label>
<Form.Control asChild>
<input type="email" required />
</Form.Control>
<Form.Message match="valueMissing">
Поле обязательно
</Form.Message>
<Form.Message match="typeMismatch">
Неверный формат email
</Form.Message>
</Form.Field>
</Form.Root>
);
}
Особенности архитектуры:
Form.Field связывает поле с валидациейForm.Message отображается только при совпадении
состоянияRadix UI использует стандартный API браузера:
HTMLInputElement.validity
Этот объект содержит множество флагов:
| Свойство | Значение |
|---|---|
valueMissing |
обязательное поле не заполнено |
typeMismatch |
неверный тип |
patternMismatch |
несоответствие регулярному выражению |
tooShort |
слишком короткое значение |
tooLong |
слишком длинное значение |
rangeUnderflow |
значение меньше минимума |
rangeOverflow |
значение больше максимума |
Radix UI позволяет отображать сообщение для любого из этих состояний.
<Form.Message match="tooShort">
Минимум 8 символов
</Form.Message>
Иногда стандартных проверок недостаточно. В таких случаях используется JavaScript-логика.
Пример проверки длины пароля.
function validatePassword(value) {
if (value.length < 8) {
return "Пароль слишком короткий";
}
return null;
}
Интеграция в компонент:
function PasswordField() {
const [error, setError] = React.useState(null);
function handleChange(e) {
const value = e.target.value;
setError(validatePassword(value));
}
return (
<Form.Field name="password">
<Form.Label>Пароль</Form.Label>
<Form.Control asChild>
<input type="password" onCha nge={handleChange} />
</Form.Control>
{error && <div className="error">{error}</div>}
</Form.Field>
);
}
В сложных формах используется управление состоянием через React.
function FormExample() {
const [value, setValue] = React.useState("");
return (
<Form.Field name="username">
<Form.Label>Имя пользователя</Form.Label>
<Form.Control asChild>
<input
value={value}
onCha nge={(e) => setValue(e.target.value)}
/>
</Form.Control>
</Form.Field>
);
}
Такой подход позволяет:
Radix UI использует нативное событие формы.
<Form.Root
onSub mit={(event) => {
event.preventDefault();
console.log("submit");
}}
>
Типичный алгоритм:
Пример проверки доступности имени пользователя.
async function checkUsername(username) {
const response = await fetch(`/api/check?username=${username}`);
const data = await response.json();
return data.available;
}
Интеграция:
function UsernameField() {
const [error, setError] = React.useState(null);
async function handleBlur(e) {
const available = await checkUsername(e.target.value);
if (!available) {
setError("Имя уже занято");
} else {
setError(null);
}
}
return (
<Form.Field name="username">
<Form.Label>Username</Form.Label>
<Form.Control asChild>
<input onB lur={handleBlur} />
</Form.Control>
{error && <div>{error}</div>}
</Form.Field>
);
}
Асинхронная валидация обычно выполняется:
HTML поддерживает атрибут pattern.
<input pattern="[A-Za-z0-9]{6,}" required />
Radix UI позволяет реагировать на эту ошибку.
<Form.Message match="patternMismatch">
Минимум 6 символов без специальных знаков
</Form.Message>
При работе с большими формами обычно используются специализированные библиотеки:
Radix UI отлично интегрируется с ними благодаря свойству
asChild.
Установка:
npm install react-hook-form
Пример:
import { useForm } from "react-hook-form";
import * as Form from "@radix-ui/react-form";
function Example() {
const { register, handleSubmit, formState } = useForm();
return (
<Form.Root onSub mit={handleSubmit(console.log)}>
<Form.Field name="email">
<Form.Label>Email</Form.Label>
<Form.Control asChild>
<input
{...register("email", {
required: "Обязательное поле"
})}
/>
</Form.Control>
{formState.errors.email && (
<div>{formState.errors.email.message}</div>
)}
</Form.Field>
</Form.Root>
);
}
Иногда требуется проверка нескольких полей.
Пример — подтверждение пароля.
function validate(password, confirm) {
if (password !== confirm) {
return "Пароли не совпадают";
}
return null;
}
Использование:
const error = validate(password, confirmPassword);
Групповая логика обычно размещается:
Ошибки должны быть:
Radix UI автоматически связывает:
labelinputerror messageЭто важно для screen readers.
Radix UI автоматически применяет:
aria-invalidaria-describedbylabel и inputПример:
<input aria-invalid="true" aria-describedby="error-id">
Это позволяет:
Radix UI добавляет атрибуты состояния:
data-invalid
data-valid
Пример CSS:
input[data-invalid] {
border-color: red;
}
.error {
color: red;
font-size: 14px;
}
Часто используется проверка на вводе.
function handleChange(e) {
const value = e.target.value;
if (value.length < 3) {
setError("Минимум 3 символа");
} else {
setError(null);
}
}
Преимущества:
Недостатки:
Поэтому часто используется debounce.
Позволяет откладывать проверку.
const debouncedValidate = debounce(validate, 500);
Это снижает:
Крупные приложения обычно используют архитектуру:
UI (Radix)
↓
Form state
↓
Validation schema
↓
API validation
Типичный стек:
Radix UI
React Hook Form
Zod
Пример схемы Zod:
const schema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
1. Разделение логики
UI не должен содержать бизнес-валидацию.
2. Использование схем
Schema-validation упрощает поддержку.
3. Минимизация асинхронных запросов
Использование debounce.
4. Чёткие сообщения
Ошибка должна объяснять:
5. Повторная проверка на сервере
Клиентская валидация — лишь удобство интерфейса.
Одна и та же проверка:
Это усложняет поддержку.
Без серверной проверки безопасность невозможна.
Сообщения вида:
Invalid input
не дают понимания проблемы.
Может приводить к:
Radix UI предоставляет:
Валидация при этом строится как отдельный слой приложения, позволяющий использовать любые подходы проверки данных без ограничений со стороны интерфейсных компонентов.