useComboBox для комбобоксов

Хук useComboBox является ключевым инструментом для создания доступных комбобоксов в приложениях на React. Он обеспечивает правильное управление фокусом, клавиатурными событиями, взаимодействие с пользователем и интеграцию с доступностью (ARIA) без необходимости ручного написания всей логики.


Импорт и базовая структура

Для использования хука необходимо подключить его из пакета @react-aria/combobox:

import { useComboBox } from '@react-aria/combobox';
import { useComboBoxState } from '@react-stately/combobox';
import { useButton } from '@react-aria/button';
import { useListBox } from '@react-aria/listbox';
import { useOverlay, useOverlayPosition } from '@react-aria/overlays';

Типичный комбобокс состоит из:

  1. Input — поле ввода текста.
  2. Button — кнопка для открытия/закрытия списка.
  3. ListBox — выпадающий список опций.
  4. Overlay — контейнер для позиционирования списка относительно поля ввода.

Хук useComboBox объединяет состояние, управление событиями и ARIA-атрибуты, чтобы все компоненты работали корректно вместе.


Создание состояния комбобокса

Состояние комбобокса управляется через useComboBoxState из React Stately:

const state = useComboBoxState({
  items: ['Apple', 'Banana', 'Cherry', 'Date'],
  defaultFilter: 'startsWith', // фильтрация опций по началу текста
  allowsCustomValue: false      // разрешить ввод только из списка
});

Ключевые свойства состояния:

  • selectedItem — текущая выбранная опция.
  • inputValue — текст в поле ввода.
  • isOpen — состояние открытия списка.
  • highlightedIndex — индекс опции, находящейся под курсором или выбранной через клавиатуру.
  • setInputValue — функция для изменения значения поля ввода.

Настройка элементов

Поле ввода

const { inputProps, listBoxProps, labelProps, buttonProps } = useComboBox({
  inputRef,
  buttonRef,
  listBoxRef,
  popoverRef
}, state);
  • inputProps — включает обработку клавиатуры, фокус и ARIA-атрибуты.
  • labelProps — связывает текстовую метку с полем ввода.
  • buttonProps — управление кнопкой открытия списка.
  • listBoxProps — управление поведением и доступностью выпадающего списка.

Пример поля ввода:

<input {...inputProps} ref={inputRef} />

Кнопка открытия списка

<button {...buttonProps} ref={buttonRef} aria-label="Выбрать опцию">
  ▼
</button>

Кнопка синхронизируется с состоянием через state.toggleOpen(). Хук автоматически добавляет обработку клавиатуры (Enter, Space) и поддержку экранных читалок.


Список опций и Overlay

Список опций размещается внутри Overlay для правильного позиционирования:

import { Overlay, OverlayContainer } from '@react-aria/overlays';

<OverlayContainer>
  {state.isOpen && (
    <Overlay {...overlayProps} ref={popoverRef}>
      <ul {...listBoxProps} ref={listBoxRef}>
        {state.collection.map((item) => (
          <li key={item.key} {...item.props}>
            {item.rendered}
          </li>
        ))}
      </ul>
    </Overlay>
  )}
</OverlayContainer>
  • Overlay автоматически управляет фокусом и предотвращает взаимодействие с элементами за пределами комбобокса.
  • useOverlayPosition позволяет задать смещение и ориентацию списка относительно поля ввода.

Клавиатурные взаимодействия

Хук useComboBox обрабатывает стандартные клавиши:

  • ArrowDown / ArrowUp — перемещение по списку.
  • Enter — выбор опции.
  • Escape — закрытие списка.
  • Tab — переход между элементами формы.

Для кастомизации можно использовать дополнительные обработчики через onKeyDown.


Фильтрация и динамические данные

С помощью React Stately можно легко интегрировать динамические источники данных:

const state = useComboBoxState({
  items: asyncItems, // массив, получаемый через API
  defaultFilter: (textValue, item) => item.startsWith(textValue),
});

Фильтрация обновляет state.collection, автоматически изменяя доступные опции в списке.


Доступность (ARIA)

useComboBox автоматически добавляет необходимые атрибуты:

  • role="combobox" для контейнера.
  • aria-expanded для состояния открытия.
  • aria-controls и aria-labelledby для связи поля ввода с списком и меткой.
  • aria-activedescendant для подсветки выбранной опции.
  • Поддержка экранных читалок и навигации только с клавиатуры.

Расширенные возможности

  1. allowsCustomValue — разрешает ввод значений, отсутствующих в списке.
  2. menuTrigger — контролирует поведение открытия списка (input, manual).
  3. shouldFocusOnHover — управление подсветкой опций при наведении мыши.
  4. scrollRef — автоматическая прокрутка выделенной опции в видимую область.

Пример полного комбобокса

function FruitComboBox() {
  const inputRef = useRef();
  const buttonRef = useRef();
  const listBoxRef = useRef();
  const popoverRef = useRef();

  const state = useComboBoxState({
    items: ['Apple', 'Banana', 'Cherry', 'Date'],
    allowsCustomValue: false
  });

  const { inputProps, listBoxProps, labelProps, buttonProps } = useComboBox({
    inputRef,
    buttonRef,
    listBoxRef,
    popoverRef
  }, state);

  return (
    <div>
      <label {...labelProps}>Фрукты</label>
      <div style={{ display: 'flex' }}>
        <input {...inputProps} ref={inputRef} />
        <button {...buttonProps} ref={buttonRef}>▼</button>
      </div>
      {state.isOpen && (
        <div ref={popoverRef}>
          <ul {...listBoxProps} ref={listBoxRef}>
            {state.collection.map(item => (
              <li key={item.key} {...item.props}>{item.rendered}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Этот пример демонстрирует правильное взаимодействие всех частей комбобокса: состояния, поля ввода, кнопки и выпадающего списка с поддержкой клавиатуры и ARIA.


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

  • Для больших списков рекомендуется использовать виртуализацию через react-virtual или аналогичные библиотеки.
  • Фильтрацию лучше выполнять на уровне состояния, чтобы минимизировать перерендер каждого элемента списка.
  • Избегать лишних вызовов setInputValue в рендере, использовать события ввода (onInputChange).

Хук useComboBox обеспечивает полную готовую к использованию архитектуру комбобоксов, позволяя создавать сложные компоненты с доступностью, динамическими данными и поддержкой клавиатуры без написания повторяющейся логики вручную.