useOutsideEvent
Описание
useOutsideEvent — хук, который подписывается на событие на уровне document и вызывает обработчик только если событие произошло вне переданного элемента (ref). Подходит для закрытия модалок/дропдаунов по клику снаружи, обработки «наружных» mousedown/touchstart и т.п.
Сигнатура
ts
function useOutsideEvent<
ElementType extends HTMLElement,
EventType extends UseOutsideEventType,
>(
ref: RefObject<ElementType | null>,
eventType: EventType,
handler: UseOutsideEventHandler<EventType>,
): void;Параметры
ref— ссылка на целевой DOM‑элемент, для которого нужно детектировать «внешние» события.eventType— тип DOM‑события, например'click' | 'mousedown' | 'pointerdown' | 'touchstart'и т.д.handler— колбэк, вызываемый только когда событие произошло внеref.current.
Возвращает:
void.
Примеры
1) Закрытие дропдауна кликом снаружи
tsx
const ref = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false);
useOutsideEvent(ref, 'click', () => setOpen(false));
return (
<div>
<button onClick={() => setOpen((v) => !v)}>Toggle</button>
{open && (
<div ref={ref} role="menu">
{' '}
...{' '}
</div>
)}
</div>
);2) Реакция на «ранние» взаимодействия
tsx
// Сработает во время нажатия, ещё до click
useOutsideEvent(ref, 'mousedown', onOutsidePress);
// На сенсорных — аналогично
useOutsideEvent(ref, 'touchstart', onOutsideTouch);3) Несколько типов событий
tsx
useOutsideEvent(ref, 'pointerdown', onOutside);
useOutsideEvent(ref, 'keydown', (e) => {
if (e.key === 'Escape') onOutside(e);
});Поведение
Внешность события
- Событие считается «внешним», если
!element.contains(event.target as Element).
- Событие считается «внешним», если
Подписка/очистка
- Подписка происходит на
documentпри маунте/когдаref.currentустановлен. - Очистка выполняется при размонтировании и при смене
eventType.
- Подписка происходит на
Актуальный обработчик
- Через
useLiveRefвызывается последняя версияhandler(без повторной подписки при его изменениях).
- Через
Зависимости
- Переподписка происходит при смене
eventType. Сменаhandlerне триггерит переподписку.
- Переподписка происходит при смене
SSR‑безопасность
- Сайд‑эффект выполняется только в браузере (в рамках
useEffect/useRefEffect).
- Сайд‑эффект выполняется только в браузере (в рамках
Когда использовать
- Закрытие поповеров/дропдаунов/меню кликом снаружи.
- Блокировка взаимодействий вне конкретной области.
- Отмена жестов/действий при клике вне компонента.
Когда не использовать
- Если оверлей/контент отрисован через портал, который логически относится к виджету, но вне DOM‑дерева элемента —
containsвернётfalse, и событие будет считаться «внешним». В таких случаях используйте общий контейнер‑refлибо собственную логику. - Если нужно ловить события только внутри элемента — используйте обычные обработчики на самом элементе.
Частые ошибки
ref.currentравенnull- Подписка не произойдёт, пока элемент не смонтирован. Убедитесь, что
refприкреплён к нужному узлу.
- Подписка не произойдёт, пока элемент не смонтирован. Убедитесь, что
Неподходящий тип события
clickсрабатывает позже, чемmousedown/pointerdown. Для моментальной реакции выбирайте «down»‑события.
Остановка всплытия
- Если внутри элемента вызывается
event.stopPropagation(), событие всё равно всплывёт доdocument? Нет — оно будет остановлено и до обработчика не дойдёт. Проверьте обработчики внутри.
- Если внутри элемента вызывается
Shadow DOM
containsза пределами того же дерева теневого DOM может работать не так, как ожидается. При работе с Shadow DOM учитывайте композицию событий.
Типизация
Экспортируемые типы
UseOutsideEventHandler<EventType extends UseOutsideEventType = UseOutsideEventType>- Колбэк, вызываемый при срабатывании указанного DOM-события:
(event: GlobalEventHandlersEventMap[EventType]) => void.
- Колбэк, вызываемый при срабатывании указанного DOM-события:
UseOutsideEventType- Объединение всех стандартных DOM-событий (
keyof GlobalEventHandlersEventMap). - Ограничивает список событий, доступных для использования в
useOutsideEvent.
- Объединение всех стандартных DOM-событий (