useFocusWithin для контейнеров

useFocusWithin — это хук из библиотеки React Aria, предназначенный для отслеживания фокуса внутри контейнера и управления соответствующими состояниями. Он полезен в интерфейсах, где требуется визуально или логически реагировать на фокус дочерних элементов, например, при создании кастомных форм, списков или интерактивных панелей.


Основные концепции

Фокус внутри контейнера определяется как состояние, когда хотя бы один элемент внутри контейнера получил фокус. В отличие от стандартного события onFocus, которое срабатывает на отдельном элементе, useFocusWithin отслеживает весь контейнер и его потомков, предоставляя удобный способ централизованной обработки фокуса.

Ключевые возможности хука:

  • Определение, находится ли фокус внутри контейнера (isFocusWithin).
  • Обработка событий фокуса и блюра для всех дочерних элементов.
  • Возможность передачи кастомных обработчиков через onFocusWithinChange.
  • Поддержка управления фокусом с клавиатуры и мыши без конфликтов с нативными событиями.

Импорт и базовое использование

import { useFocusWithin } from '@react-aria/interactions';
import { useRef } from 'react';

function FocusContainer() {
  const ref = useRef();
  const { focusWithinProps, isFocusWithin } = useFocusWithin({
    onFocusWithinChange: (isFocused) => console.log('Фокус внутри:', isFocused)
  });

  return (
    <div {...focusWithinProps} ref={ref} style={{
      border: isFocusWithin ? '2px solid blue' : '2px solid gray',
      padding: '16px',
      borderRadius: '8px'
    }}>
      <input type="text" placeholder="Имя" />
      <input type="text" placeholder="Фамилия" />
    </div>
  );
}

Разбор кода:

  • ref привязывает контейнер к DOM-элементу.
  • focusWithinProps включает все необходимые обработчики событий (onFocus, onBlur) для отслеживания фокуса.
  • isFocusWithin — булевое значение, которое становится true, если любой дочерний элемент контейнера получил фокус.
  • Стили динамически меняются в зависимости от состояния фокуса.

Обработка событий фокуса внутри контейнера

Хук позволяет задать функции обратного вызова:

  • onFocusWithinChange – вызывается при каждом изменении состояния фокуса внутри контейнера. Аргумент isFocused указывает, находится ли фокус внутри контейнера.
  • onFocusWithin и onBlurWithin – отдельные события для начала и окончания фокуса внутри контейнера.
const { focusWithinProps } = useFocusWithin({
  onFocusWithin: () => console.log('Фокус появился внутри контейнера'),
  onBlurWithin: () => console.log('Фокус покинул контейнер')
});

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


Стилизация состояния фокуса

Использование isFocusWithin удобно для динамической стилизации контейнера:

<div {...focusWithinProps} ref={ref} className={isFocusWithin ? 'focused' : ''}>
  ...
</div>

В CSS:

.focused {
  border: 2px solid #3b82f6;
  box-shadow: 0 0 4px rgba(59,130,246,0.5);
}

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


Композиция с другими хуками React Aria

useFocusWithin отлично сочетается с:

  • usePress — для отслеживания нажатий внутри контейнера.
  • useHover — для комбинирования фокуса и наведения.
  • useOverlay — для управления фокусом в модальных окнах и всплывающих панелях.
import { useFocusWithin, useHover } from '@react-aria/interactions';

const { focusWithinProps, isFocusWithin } = useFocusWithin();
const { hoverProps, isHovered } = useHover();

<div {...focusWithinProps} {...hoverProps}>
  {isFocusWithin && <span>Фокус внутри</span>}
  {isHovered && <span>Наведение</span>}
</div>

Такой подход упрощает создание сложных интерактивных компонентов с несколькими состояниями.


Практические советы

  1. Использовать ref на контейнере, а не на отдельных элементах. Хук работает на всей группе дочерних элементов.
  2. Не дублировать события onFocus и onBlur на потомках, чтобы не нарушать логику хука.
  3. Сочетать с CSS-псевдоклассами :focus-within, если необходима только визуальная подсветка, но useFocusWithin позволяет также управлять логикой компонента.
  4. Использовать с формами и списками, где важно знать, активен ли блок, без добавления состояния каждому элементу отдельно.

Пример: форма с подсветкой активного блока

function ProfileForm() {
  const nameRef = useRef();
  const emailRef = useRef();

  const nameFocus = useFocusWithin({ ref: nameRef });
  const emailFocus = useFocusWithin({ ref: emailRef });

  return (
    <form>
      <div {...nameFocus.focusWithinProps} ref={nameRef} className={nameFocus.isFocusWithin ? 'active' : ''}>
        <input type="text" placeholder="Имя" />
      </div>
      <div {...emailFocus.focusWithinProps} ref={emailRef} className={emailFocus.isFocusWithin ? 'active' : ''}>
        <input type="email" placeholder="Email" />
      </div>
    </form>
  );
}

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


useFocusWithin позволяет создать единый источник правды о состоянии фокуса внутри контейнера, упрощает управление интерактивными компонентами и снижает количество ручного кода для отслеживания активности пользователя. Такой подход особенно ценен при создании доступных интерфейсов и кастомных UI-компонентов, где важно корректное управление фокусом.