惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
H
Help Net Security
Jina AI
Jina AI
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
博客园 - 叶小钗
Y
Y Combinator Blog
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
C
Check Point Blog
Recent Announcements
Recent Announcements
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
B
Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Настройка типизации формы React Hook Form (≥ v7.44.0) + Z...
EkaterinaSL · 2026-05-23 · via Все публикации подряд на Хабре

Уровень сложностиСложный

Время на прочтение2 мин

Охват и читатели0

Туториал

Работая с формой, часто нам нужно сделать так, чтобы на вход она принимала данные одного типа, а после валидации их тип меняется

Моя форма состоит из полей, начальное значение которых - пустая строка, а после валидации - число

Давайте попробуем создать схему для такой формы и вывести из нее тип

const EMPTY_NUMERIC = '';

const numericScheme = z.union([z.number(), z.literal('')]);

type NumericValue = z.infer<typeof numericScheme>;

const requiredNumberWithRefine = numericScheme.refine(
  (val) => val !== EMPTY_NUMERIC,
  {
    message: 'Это поле обязательно для заполнения',
  },
);

export const formSchema = z.object({
  amount: requiredNumberWithRefine,
  quantity: requiredNumberWithRefine,
});

export type FormDataInfer = z.infer<typeof formSchema>;

Стандартный способ с infer не подойдет в таком случае, при попытке типизировать форму и задать начальные значения в хуке useForm посыпятся ошибки типизации

Начиная с v7.44.0 (релиз) React Hook Form хук useForm стал выглядеть так

 useForm<TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>

Появился 3-ий дженерик TTransformedValues - он определяет выходные параметры формы, после их модификации

В нашей ситуации мы можем сузить наш начальный тип NumericValue до number с помощью одного из методов - refine, transform, superRefine с pipe. Мы уже используем refine в схеме requiredNumberWithRefine, он подходит, но приведу примеры c transform и superRefine+pipe*, если, например, вы захотите использовать контекст

* если использовать только superRefine, тип не будет сужен

const requiredNumberWithTransform = numericScheme.transform(
  (val: NumericValue, ctx: z.RefinementCtx) => {
    if (val === EMPTY_NUMERIC) {
      ctx.addIssue({
        code: 'custom',
        message: 'Это поле обязательно для заполнения',
      });
      return z.NEVER;
    }
    return val;
  },
);

const requiredNumberWithSuperRefineAndPipe = numericScheme
  .superRefine((val, ctx) => {
    if (val === EMPTY_NUMERIC) {
      ctx.addIssue({
        code: 'custom',
        message: 'Это поле обязательно для заполнения',
      });
    }
  })
  .pipe(z.number());

Далее выводим 2 отдельных типа FormDataInput и FormDataOutput с помощью дженериков  z.input и z.output 

export type FormDataInput = z.input<typeof formSchema>;
export type FormDataOutput = z.output<typeof formSchema>;

Если мы посмотрим, какие типы получились, то увидим

Теперь типизируем defaultValues с помощью FormDataInput, используем эти дженерики в хуке useForm (если мы не добавим типы в дженерики явно, они будут выведены самостоятельно, но давайте сделаем это для наглядности), в хендлере onSubmit используем FormDataOutput

Выглядеть это будет так:

const defaultValues: FormDataInput = {
  amount: '',
  quantity: '',
};


const Form = () => {
  const {
    handleSubmit,
    control,
    formState: { errors },
  } = useForm<FormDataInput, unknown, FormDataOutput>({
    resolver: zodResolver(formSchema),
    defaultValues,
  });

  const onSubmit = (data: FormDataOutput) => {
    saveData(data);
  };

  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      ...

Все, теперь явно видно какие параметры получает форма на входе и выходе, плюс мы можем отделять части формы в отдельные константы, файлы и безболезненно их типизировать