Установка и подключение связки с использованием резолвера для схем
валидации на основе Joi строится вокруг интеграции с React Hook Form
через пакет адаптеров резолверов, предоставляемый экосистемой
@hookform/resolvers.
Механизм Joi resolver представляет собой функцию-обёртку, преобразующую синтаксис схемы Joi в формат, совместимый с внутренним контрактом React Hook Form. Основная задача заключается в унификации результатов валидации: ошибок, значений и метаданных формы.
React Hook Form использует ленивую модель регистрации полей и минимальное количество перерисовок. В этой модели валидация выносится за пределы ядра и подключается через резолверы.
Joi resolver выполняет три ключевые функции:
FieldErrors).Ключевой принцип заключается в том, что React Hook Form не зависит от Joi напрямую. Связь реализуется через адаптер.
Экосистема требует наличия трёх основных компонентов:
@hookform/resolversУстановка:
npm install react-hook-form joi @hookform/resolvers
Joi строит схемы декларативно, описывая ограничения на уровне типов и правил.
import Joi from "joi";
const schema = Joi.object({
email: Joi.string().email({ tlds: false }).required(),
password: Joi.string().min(8).max(32).required()
});
Каждое поле описывается цепочкой методов:
string() — тип строкиemail() — проверка email форматаmin() и max() — ограничения длиныrequired() — обязательность поляИнтеграция осуществляется через функцию joiResolver.
import { useForm } from "react-hook-form";
import { joiResolver } from "@hookform/resolvers/joi";
import Joi from "joi";
const schema = Joi.object({
email: Joi.string().email({ tlds: false }).required(),
password: Joi.string().min(8).required()
});
const form = useForm({
resolver: joiResolver(schema)
});
В этом случае вся валидация формы делегируется Joi, а React Hook Form получает стандартизированные результаты.
Joi возвращает объект ошибки, содержащий массив деталей
(details). Каждая ошибка имеет:
path) к полюmessage)type)Joi resolver преобразует это в структуру:
{
fieldName: {
type: "validation_type",
message: "Описание ошибки"
}
}
Пример поведения:
const schema = Joi.object({
username: Joi.string().alphanum().min(3).required()
});
При вводе ab будет сформирована ошибка:
username"length must be at least 3 characters long"React Hook Form использует регистрацию полей без controlled state:
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: joiResolver(schema)
});
return (
<form onSub mit={handleSubmit(data => console.log(data))}>
<input {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
</form>
);
}
Здесь Joi resolver выполняется при сабмите или при изменении (в
зависимости от режима mode).
React Hook Form поддерживает несколько стратегий:
onSubmit — проверка при отправке формыonBlur — проверка при потере фокусаonChange — проверка при вводеall — комбинацияПример:
useForm({
resolver: joiResolver(schema),
mode: "onBlur"
});
Joi resolver при этом вызывается в соответствии с жизненным циклом формы.
Joi поддерживает вложенные структуры, что критично для сложных форм.
const schema = Joi.object({
user: Joi.object({
name: Joi.string().required(),
age: Joi.number().min(18)
})
});
React Hook Form отражает это через точечную нотацию:
register("user.name");
register("user.age");
Ошибки возвращаются в аналогичной структуре:
errors.user?.name?.message
Joi позволяет описывать массивы через array():
const schema = Joi.object({
tags: Joi.array().items(Joi.string().min(2))
});
В React Hook Form используется useFieldArray:
const { fields, append } = useFieldArray({
name: "tags",
control
});
Joi resolver обрабатывает каждый элемент массива отдельно и агрегирует ошибки по индексам.
Joi поддерживает асинхронные правила через кастомные валидаторы:
const schema = Joi.object({
username: Joi.string().external(async (value) => {
const exists = await checkUsername(value);
if (exists) throw new Error("Username already exists");
})
});
Joi resolver ожидает завершения Promise и передаёт результат обратно в React Hook Form.
Joi resolver выполняет полную проверку схемы при каждом вызове. Это важно учитывать при:
onChangeОптимизация достигается через:
onBluruseMemoПример:
const schema = useMemo(() => Joi.object({...}), []);
Joi позволяет централизованно задавать сообщения:
const schema = Joi.object({
email: Joi.string()
.email()
.messages({
"string.email": "Некорректный email",
"string.empty": "Email обязателен"
})
});
Joi resolver сохраняет эти сообщения без изменений, что делает возможной унификацию UX-логики.
В связке с TypeScript типизация формы выводится из схемы через дженерики:
type FormValues = {
email: string;
password: string;
};
const schema: Joi.ObjectSchema<FormValues> = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).required()
});
React Hook Form:
const { register } = useForm<FormValues>({
resolver: joiResolver(schema)
});
Типы обеспечивают согласованность между UI и схемой валидации, снижая вероятность рассинхронизации.
В экосистеме резолверов существуют альтернативы:
Особенности Joi resolver:
При этом Joi чаще используется в backend-ориентированных архитектурах, что делает резолвер полезным в унифицированных full-stack приложениях.
Joi позволяет трансформировать данные до валидации:
const schema = Joi.object({
email: Joi.string().trim().lowercase().email().required()
});
В этом случае resolver получает уже нормализованное значение, что снижает нагрузку на UI-логику.
Также возможно использование alter() для динамического
изменения схем:
const schema = Joi.object({
password: Joi.string().min(8)
}).tailor("signup");
Joi resolver часто используется как единый слой валидации, синхронизирующий frontend и backend.
Типичный сценарий:
Это уменьшает риск расхождения логики между слоями приложения.
При частичной валидации Joi resolver возвращает все найденные ошибки, не прерывая выполнение на первой:
abortEarly: false
Это поведение соответствует требованиям UX форм, где важно показать весь список проблем сразу.
Joi формирует путь ошибки в виде массива:
["user", "address", "city"]
Resolver преобразует его в строку:
"user.address.city"
Это обеспечивает совместимость с React Hook Form и позволяет корректно отображать ошибки в глубоко вложенных структурах.
Joi поддерживает зависимости между полями:
const schema = Joi.object({
password: Joi.string().required(),
confirmPassword: Joi.any().valid(Joi.ref("password")).required()
});
Joi resolver корректно интерпретирует такие зависимости, обеспечивая кросс-полевую проверку без дополнительной логики в компоненте формы.
При использовании reset() React Hook Form очищает
состояние, но схема Joi остаётся неизменной. Resolver не хранит
состояние между вызовами, что делает его полностью чистой функцией.
Joi resolver выполняет функцию промежуточного слоя между декларативной схемой данных и реактивной формой. Он позволяет: