Strict режим

Strict режим в библиотеке Yup управляет поведением преобразования данных перед валидацией. По умолчанию Yup старается автоматически привести значения к ожидаемому типу: строки превращаются в числа, числа — в строки, даты парсятся из текста. Такое поведение называется casting.

При использовании yupResolver вместе с React Hook Form это может приводить к ситуациям, когда данные проходят проверку, хотя фактически имеют неверный тип.

Strict режим отключает автоматическое преобразование типов и заставляет Yup проверять значения «как есть».


Поведение Yup без strict режима

Стандартная схема:

import * as yup from 'yup';

const schema = yup.object({
  age: yup.number().required(),
});

Проверка:

await schema.validate({
  age: '25',
});

Результат:

{ age: 25 }

Хотя значение было строкой, Yup автоматически преобразовал его в число.


Проблемы автоматического преобразования

Автоматический casting может скрывать ошибки:

await schema.validate({
  age: '999abc',
});

Иногда Yup пытается преобразовать значение и возвращает неожиданные результаты.

Другой пример:

const schema = yup.object({
  enabled: yup.boolean(),
});

await schema.validate({
  enabled: 'true',
});

Результат:

{ enabled: true }

Фактически API получил строку, а не boolean.

В крупных приложениях это приводит к:

  • ошибкам сериализации;
  • проблемам API-контрактов;
  • несовместимости TypeScript-типов;
  • скрытым багам в формах;
  • различиям между frontend и backend валидацией.

Включение strict режима

Strict режим активируется методом .strict():

const schema = yup.object({
  age: yup.number().strict().required(),
});

Теперь:

await schema.validate({
  age: '25',
});

Вызывает ошибку:

age must be a `number` type

Преобразование строки в число больше не выполняется.


Strict для всей схемы

Strict можно включить глобально:

const schema = yup
  .object({
    age: yup.number().required(),
    name: yup.string().required(),
  })
  .strict();

Теперь все поля внутри объекта проверяются без casting.


Работа strict внутри yupResolver

Базовое подключение:

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const schema = yup
  .object({
    age: yup.number().strict().required(),
  });

const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm({
  resolver: yupResolver(schema),
});

Если поле age содержит строку "20", валидация завершится ошибкой.


Разница между strict и non-strict

Без strict

const schema = yup.object({
  age: yup.number(),
});

await schema.validate({
  age: '18',
});

Результат:

{ age: 18 }

Со strict

const schema = yup.object({
  age: yup.number().strict(),
});

await schema.validate({
  age: '18',
});

Ошибка:

age must be a `number` type

Проверка типов без преобразования

Strict режим особенно полезен для API-валидации.

Например:

const schema = yup.object({
  id: yup.number().strict(),
  isAdmin: yup.boolean().strict(),
});

Данные:

{
  id: "5",
  isAdmin: "false"
}

Обе проверки завершатся ошибкой.

Это гарантирует:

  • корректность payload;
  • предсказуемость типов;
  • безопасность данных;
  • соответствие backend-контракту.

Strict и transform

Метод transform() перестаёт автоматически вызываться так же, как в обычном режиме.

Пример:

const schema = yup.string().transform(value => {
  return value.trim();
});

В non-strict режиме:

await schema.validate(' hello ');

Результат:

"hello"

Со strict:

const schema = yup
  .string()
  .transform(value => value.trim())
  .strict();

Поведение меняется — Yup перестаёт выполнять часть внутренних преобразований.


Strict и trim

Методы:

  • .trim()
  • .lowercase()
  • .uppercase()

внутри Yup являются transform-операциями.

Пример:

const schema = yup.string().trim().strict();

Проверка:

await schema.validate(' hello ');

Ошибка:

this must be a trimmed string

Yup больше не исправляет значение автоматически, а требует уже корректные данные.


Strict и lowercase

const schema = yup.string().lowercase().strict();

Проверка:

await schema.validate('ADMIN');

Ошибка:

this must be a lowercase string

Без strict значение автоматически стало бы "admin".


Strict и uppercase

const schema = yup.string().uppercase().strict();

Проверка:

await schema.validate('hello');

Ошибка:

this must be a upper case string

Strict и даты

Без strict:

const schema = yup.date();

await schema.validate('2025-01-01');

Yup автоматически создаёт объект Date.

Со strict:

const schema = yup.date().strict();

await schema.validate('2025-01-01');

Ошибка:

this must be a `date` type

Strict и nullable

const schema = yup
  .number()
  .nullable()
  .strict();

Допустимые значения:

null
42

Недопустимо:

"42"

Strict и optional

const schema = yup.object({
  age: yup.number().strict().optional(),
});

Допустимо:

{}

Недопустимо:

{
  age: "20"
}

Комбинация strict и required

const schema = yup.object({
  age: yup.number().strict().required(),
});

Проверка:

{
  age: undefined
}

Ошибка:

age is a required field

Проверка:

{
  age: "25"
}

Ошибка:

age must be a `number` type

Strict и nested объекты

const schema = yup.object({
  profile: yup.object({
    age: yup.number().strict(),
  }),
});

Проверка:

{
  profile: {
    age: "30"
  }
}

Ошибка будет сгенерирована внутри вложенного объекта.


Strict и массивы

const schema = yup.array(
  yup.number().strict()
);

Проверка:

["1", "2", 3]

Ошибки:

[0] must be a `number` type
[1] must be a `number` type

Strict и mixed

const schema = yup.mixed().strict();

mixed() допускает любые значения, но strict всё равно отключает преобразования.


Strict и кастомные test

const schema = yup
  .number()
  .strict()
  .test(
    'positive',
    'Must be positive',
    value => value > 0
  );

Проверка:

"10"

До кастомного test() выполнение не дойдёт — ошибка типа возникнет раньше.


Strict и TypeScript

Strict режим особенно важен при использовании TypeScript.

Без strict:

const schema = yup.object({
  age: yup.number(),
});

TypeScript считает:

age: number

Но runtime допускает:

{
  age: "20"
}

Возникает рассинхронизация типов.

Со strict runtime начинает соответствовать TypeScript-описанию.


Strict и серверная валидация

На backend strict почти всегда предпочтителен.

Причины:

  • данные не должны изменяться автоматически;
  • API обязан получать корректные типы;
  • скрытые преобразования усложняют отладку;
  • безопасность становится выше.

Пример:

const userSchema = yup.object({
  id: yup.number().strict(),
  email: yup.string().email().strict(),
  active: yup.boolean().strict(),
});

Strict и формы

Во frontend-формах strict используют осторожно.

Причина:

HTML input почти всегда возвращает строки.

Например:

<input type="number" />

Даже числовой input возвращает:

"25"

Из-за этого strict может постоянно генерировать ошибки.


Решение проблемы со строками из input

valueAsNumber

<input
  type="number"
  {...register('age', {
    valueAsNumber: true,
  })}
/>

Теперь React Hook Form передаст число вместо строки.

Strict будет работать корректно.


valueAsBoolean

Для checkbox:

<input
  type="checkbox"
  {...register('active')}
/>

Boolean обычно приходит корректно, поэтому strict здесь безопасен.


setValueAs

Дополнительное преобразование:

<input
  {...register('age', {
    setValueAs: value =>
      value === '' ? undefined : Number(value),
  })}
/>

Теперь schema может быть strict.


Strict и mode в React Hook Form

Strict никак не зависит от:

  • onSubmit
  • onBlur
  • onChange
  • all
  • onTouched

Он влияет исключительно на Yup validation pipeline.


Передача strict через validate options

Strict можно передать напрямую в validate():

schema.validate(data, {
  strict: true,
});

Даже если схема была создана без .strict().


Переопределение strict

const schema = yup.number();

Проверка:

schema.validate('10', {
  strict: true,
});

Ошибка типа будет выброшена.


Локальный strict для отдельных полей

const schema = yup.object({
  id: yup.number().strict(),
  name: yup.string(),
});

Только id проверяется без casting.


Проверка через isValid

const schema = yup.number().strict();

await schema.isValid('10');

Результат:

false

Без strict было бы:

true

Strict и cast

Метод cast() игнорирует strict-валидацию:

const schema = yup.number().strict();

schema.cast('10');

Результат:

10

Потому что cast() занимается именно преобразованием типов.


Различие validate и cast

validate

Проверяет корректность.

cast

Преобразует данные.

Strict влияет на validate, но не отменяет назначение cast.


Практический пример формы

const schema = yup.object({
  age: yup
    .number()
    .strict()
    .required(),
});

const form = useForm({
  resolver: yupResolver(schema),
});

Input:

<input
  type="number"
  {...register('age', {
    valueAsNumber: true,
  })}
/>

Теперь:

  • input возвращает number;
  • Yup не делает casting;
  • типы совпадают;
  • TypeScript работает корректно;
  • backend получает валидные данные.

Когда использовать strict

Strict режим особенно полезен:

  • в backend API;
  • в TypeScript-проектах;
  • при строгих DTO;
  • в enterprise-приложениях;
  • при работе с финансовыми данными;
  • в системах с высокой ценой ошибки;
  • при интеграции frontend/backend контрактов.

Когда strict может мешать

Strict режим может создавать неудобства:

  • в простых формах;
  • при большом количестве HTML input;
  • при старом коде с implicit casting;
  • при миграции legacy-проектов;
  • в UI, где нужны «мягкие» преобразования.

Практика безопасного использования

Наиболее распространённый подход:

  1. Преобразование данных выполняется на уровне формы.
  2. Yup занимается только проверкой.
  3. Strict включается глобально.
  4. Все runtime-типы синхронизированы с TypeScript.

Такой подход делает систему предсказуемой и уменьшает количество скрытых ошибок.