React Hook Form

Для интеграции Radix UI с React Hook Form необходимо установить обе библиотеки:

npm install @radix-ui/react-dropdown-menu react-hook-form

После установки импортируются необходимые компоненты и хуки:

import { useForm, Controller } from "react-hook-form";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";

useForm предоставляет базовые методы для управления формой, а Controller используется для интеграции нестандартных контролов, таких как компоненты Radix UI, которые не поддерживают стандартные HTML-события onChange и value.


Управление формой через Controller

Radix UI компоненты часто не имеют стандартного API value/onChange. Для их использования с React Hook Form применяется Controller:

<Controller
  name="fruit"
  control={control}
  defaultValue=""
  render={({ field }) => (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger>{field.value || "Выберите фрукт"}</DropdownMenu.Trigger>
      <DropdownMenu.Content>
        <DropdownMenu.Item onSel ect={() => field.onChange("Яблоко")}>Яблоко</DropdownMenu.Item>
        <DropdownMenu.Item onSel ect={() => field.onChange("Банан")}>Банан</DropdownMenu.Item>
        <DropdownMenu.Item onSel ect={() => field.onChange("Апельсин")}>Апельсин</DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  )}
/>

Ключевые моменты:

  • field.value синхронизирует выбранное значение с формой.
  • field.onChange используется для обновления состояния формы при выборе элемента.
  • Radix onSelect заменяет стандартное onChange.

Валидация и ошибки

React Hook Form поддерживает встроенную валидацию. Для Radix UI компонентов она реализуется через rules в Controller:

<Controller
  name="fruit"
  control={control}
  rules={{ required: "Выбор фрукта обязателен" }}
  render={({ field, fieldState }) => (
    <>
      <DropdownMenu.Root>
        <DropdownMenu.Trigger>{field.value || "Выберите фрукт"}</DropdownMenu.Trigger>
        <DropdownMenu.Content>
          <DropdownMenu.Item onSel ect={() => field.onChange("Яблоко")}>Яблоко</DropdownMenu.Item>
          <DropdownMenu.Item onSel ect={() => field.onChange("Банан")}>Банан</DropdownMenu.Item>
          <DropdownMenu.Item onSel ect={() => field.onChange("Апельсин")}>Апельсин</DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Root>
      {fieldState.error && <span style={{ color: "red" }}>{fieldState.error.message}</span>}
    </>
  )}
/>

Особенности:

  • fieldState.error содержит объект ошибки.
  • Сообщение ошибки можно выводить рядом с компонентом для улучшения UX.

Интеграция с другими компонентами Radix

Radix UI предлагает множество контролов: Slider, Checkbox, Switch, RadioGroup. Для каждого применяется Controller для синхронизации с формой.

Пример: Checkbox

<Controller
  name="agree"
  control={control}
  rules={{ required: "Необходимо согласие" }}
  render={({ field }) => (
    <>
      <Checkbox.Root checked={field.value} onCheckedCha nge={field.onChange}>
        <Checkbox.Indicator />
      </Checkbox.Root>
      {errors.agree && <span>{errors.agree.message}</span>}
    </>
  )}
/>

Пример: Slider

<Controller
  name="volume"
  control={control}
  defaultValue={50}
  render={({ field }) => (
    <Slider.Root value={field.value} onValueCha nge={field.onChange}>
      <Slider.Track>
        <Slider.Range />
      </Slider.Track>
      <Slider.Thumb />
    </Slider.Root>
  )}
/>

Особенности:

  • Radix Slider использует массив для диапазона, но для одиночного значения можно передавать и получать одно число.
  • onValueChange полностью заменяет стандартный onChange.

Работа с динамическими формами

React Hook Form позволяет динамически добавлять и удалять поля через useFieldArray. В комбинации с Radix UI это дает гибкость:

const { fields, append, remove } = useFieldArray({
  control,
  name: "tasks"
});

fields.map((task, index) => (
  <Controller
    key={task.id}
    name={`tasks.${index}.name`}
    control={control}
    render={({ field }) => (
      <Input value={field.value} onCha nge={field.onChange} />
    )}
  />
));

<button type="button" onCl ick={() => append({ name: "" })}>Добавить задачу</button>

Важные моменты:

  • fields содержит идентификаторы для безопасного рендера динамических компонентов.
  • append и remove управляют состоянием формы без перерендера всей формы.

Производительность и оптимизация

  • Использование Controller для каждого Radix UI компонента не снижает производительность, так как React Hook Form минимизирует перерисовку компонентов через внутренний useFormState.
  • Для сложных форм можно использовать shouldUnregister: true, чтобы не хранить ненужные поля в состоянии формы.
  • В больших формах с десятками Radix компонентов рекомендуется локальный state внутри контроллера для сложных интерактивных элементов и синхронизация с field.onChange только при изменении значения.

Настройка стилизации и состояния

Radix UI поддерживает состояние компонентов через атрибуты:

  • data-state="open" для Dropdown
  • data-disabled для элементов, которые неактивны
  • data-checked для Checkbox и Switch

Эти атрибуты позволяют легко подключать CSS или Tailwind классы для визуализации состояния формы.

[data-state="open"] {
  background-color: #f0f0f0;
}

[data-checked] {
  border-color: #4f46e5;
}

Поддержка сложных сценариев

Radix UI и React Hook Form позволяют строить формы с:

  • Множественным выбором (MultiSelect через Checkbox внутри DropdownMenu)
  • Диапазонами значений (Slider с массивом)
  • Асинхронной валидацией (например, проверка уникальности значения через API)
  • Условными полями (динамическое рендеринг на основе значения других полей)

Комбинация Controller + Radix UI делает форму полностью контролируемой, удобной для валидации и легко масштабируемой.