Хук useOutsideClick из библиотеки Chakra UI предназначен
для обработки кликов вне определённого DOM-элемента. Он используется в
ситуациях, когда необходимо реагировать на взаимодействие пользователя
за пределами компонента: закрывать выпадающие меню, модальные окна,
всплывающие панели, тултипы или кастомные контекстные элементы
интерфейса.
Типичный пользовательский сценарий:
Реализация подобного поведения вручную требует установки глобального
обработчика событий mousedown или click,
проверки event.target, а также корректной очистки
слушателей. useOutsideClick инкапсулирует эту логику и
предоставляет декларативный API.
Хук широко используется в компонентах интерфейса, построенных на основе React, поскольку позволяет управлять состоянием UI через реактивную модель.
Хук импортируется непосредственно из пакета Chakra UI:
import { useOutsideClick } from "@chakra-ui/react"
Механизм работы состоит из нескольких этапов:
Определяется DOM-элемент, внутри которого клики считаются допустимыми.
Устанавливается глобальный обработчик событий на документ.
При возникновении события происходит проверка:
event.target указанному элементу.Если клик произошёл вне области элемента, вызывается переданный обработчик.
Основная идея заключается в использовании ref,
указывающего на DOM-узел.
import { Box, Button, useOutsideClick } from "@chakra-ui/react"
import { useRef, useState } from "react"
function Dropdown() {
const ref = useRef()
const [isOpen, setIsOpen] = useState(false)
useOutsideClick({
ref: ref,
handler: () => setIsOpen(false),
})
return (
<Box>
<Button onCl ick={() => setIsOpen(!isOpen)}>
Toggle menu
</Button>
{isOpen && (
<Box ref={ref} p={4} bg="gray.100">
Dropdown content
</Box>
)}
</Box>
)
}
Структура взаимодействия:
ref;ref передаётся в элемент меню;useOutsideClick получает этот ref;ref.current, вызывается
handler.useOutsideClick принимает объект конфигурации.
refСсылка на DOM-элемент, внутри которого клики не считаются внешними.
Тип:
RefObject<HTMLElement>
Пример:
const ref = useRef()
handlerФункция, вызываемая при клике вне элемента.
useOutsideClick({
ref,
handler: () => {
console.log("Clicked outside")
},
})
Обычно обработчик изменяет состояние интерфейса — например, закрывает панель.
enabled (опционально)Позволяет включать и отключать обработчик.
useOutsideClick({
ref,
handler: closeMenu,
enabled: isOpen,
})
Когда enabled равен false, обработчик
событий не активируется.
Это важно для оптимизации, особенно при большом количестве компонентов.
Если пользователь кликает по дочернему элементу внутри
ref, событие не считается внешним.
Пример структуры DOM:
Box (ref)
├─ Button
├─ Text
└─ Icon
Любой клик внутри этой структуры не вызовет handler.
Проверка происходит через метод:
element.contains(event.target)
function MenuExample() {
const ref = useRef()
const [open, setOpen] = useState(false)
useOutsideClick({
ref,
handler: () => setOpen(false),
enabled: open,
})
return (
<Box>
<Button onCl ick={() => setOpen(true)}>Open</Button>
{open && (
<Box ref={ref} bg="white" shadow="md" p={4}>
Menu item 1
Menu item 2
</Box>
)}
</Box>
)
}
Логика работы:
useOutsideClick часто используется при создании
кастомных модальных окон.
function Modal() {
const ref = useRef()
useOutsideClick({
ref,
handler: () => closeModal(),
})
return (
<Box className="overlay">
<Box ref={ref} className="modal">
Modal content
</Box>
</Box>
)
}
В такой архитектуре:
Иногда требуется исключить несколько областей из обработки клика. В таких случаях обычно используется общий контейнер.
<Box ref={ref}>
<Input />
<Button />
<Box>Dropdown</Box>
</Box>
Все вложенные элементы будут считаться внутренними.
Компоненты Chakra UI могут рендериться через портал
(Portal), что перемещает DOM-узел за пределы текущей
иерархии.
Если элемент отображается в портале, важно учитывать:
ref должен указывать на реальный DOM-элемент
порталаcontains() не сработает корректно.Хук обычно подписывается на событие:
mousedown
или
pointerdown
Использование mousedown имеет преимущество:
click;Событийная модель:
mousedown → mouseup → click
useOutsideClick автоматически управляет жизненным циклом
слушателей событий:
Пример упрощённой внутренней логики:
useEffect(() => {
function handle(event) {
if (!ref.current?.contains(event.target)) {
handler(event)
}
}
document.addEventListener("mousedown", handle)
return () => {
document.removeEventListener("mousedown", handle)
}
}, [ref, handler])
ref)Важно, чтобы ref всегда указывал на актуальный
DOM-элемент.
Некорректный пример:
const ref = useRef(null)
но ref не передан в компонент:
<Box>
Правильный вариант:
<Box ref={ref}>
function SearchPanel() {
const ref = useRef()
const [active, setActive] = useState(false)
useOutsideClick({
ref,
handler: () => setActive(false),
})
return (
<Box ref={ref}>
<Input
onFo cus={() => setActive(true)}
placeholder="Search..."
/>
{active && (
<Box bg="white" shadow="sm">
Search results
</Box>
)}
</Box>
)
}
Особенности поведения:
useOutsideClick обычно комбинируется с
useState.
Пример:
const [isOpen, setIsOpen] = useState(false)
и обработчиком:
handler: () => setIsOpen(false)
Таким образом обеспечивается декларативное управление интерфейсом.
Хук реализован таким образом, чтобы не создавать лишних слушателей.
Рекомендации:
1. Использовать enabled
enabled: isOpen
Это предотвращает регистрацию обработчика, когда компонент закрыт.
2. Избегать лишних пересозданий handler
Желательно использовать useCallback.
const close = useCallback(() => {
setOpen(false)
}, [])
refuseOutsideClick({
ref,
handler: close,
})
но ref не передан элементу.
Если ref указывает на родительский контейнер страницы,
почти все клики будут считаться внутренними.
Компоненты, отображающиеся через портал, могут не входить в область
contains().
useOutsideClickХук применяется в следующих интерфейсных элементах:
Во всех этих случаях требуется единая логика:
клик вне элемента → изменение состояния интерфейса.
Ручной подход:
document.addEventListener("mousedown", handler)
Проблемы такого подхода:
useOutsideClick решает эти задачи, предоставляя
декларативный интерфейс.
Хук часто используется вместе с:
useDisclosureuseBooleanuseControllableStateПример:
const { isOpen, onOpen, onClose } = useDisclosure()
useOutsideClick({
ref,
handler: onClose,
})
Это формирует стандартный паттерн управления состоянием интерфейса.
useOutsideClick относится к категории
interaction hooks — хуков, управляющих поведением
пользовательских взаимодействий.
В архитектуре интерфейсов он решает задачу:
детектирование внешнего взаимодействия с компонентом.
Такая абстракция делает сложные интерактивные элементы значительно проще в реализации и поддержке.