Як реалізувати Click Outside в React
Оскільки я майже сім років працюю з Angular, я вирішив писати такі статті для тих, хто переходить на React і стикається з проблемами реалізації різних речей у своїх апках.
Під час розробки компонента Sidebar на React у мене виникла необхідність реалізувати детектинг кліку за межами цього компонента, щоб закривати його після кожного кліку мишкою десь по екрану.
В Angular я робив для цього директиву:
@Directive({ selector: '[clickOutside]' }) export class ClickOutsideDirective { // Створюємо вихідне значення, яке буде емінуватися при кліку за межами елемента readonly clickOutside = output<void>(); // Отримуємо посилання на елемент, до якого застосовується директива private readonly elementRef = inject(ElementRef) // Використовуємо HostListener для прослуховування події 'click' на документі @HostListener('document:click', ['$event.target']) public onClick(target) { // Перевіряємо, чи клікнув користувач всередині елемента const clickedInside = this.elementRef.nativeElement.contains(target); // Якщо клік був за межами елемента, емітимо подію if (!clickedInside) { this.clickOutside.emit(); } } }
Тут все зрозуміло, а тепер мені потрібно зробити щось подібне в React. Перше, що прийшло в голову — зробити кастомний хук.
Як він буде працювати? Ось його схема:
function MyComponent() { // Викликаємо кастомний хук useDetectClickOutside і передаємо конфігурацію const ref = useDetectClickOutside({ // Функція, яка буде викликана при кліку за межами елемента або натисканні клавіші onTriggered: () => console.log('Clicked outside or pressed Escape'), // Список клавіш, на які буде реагувати хук (Escape та Enter) triggerKeys: ['Escape', 'Enter'] }); // Повертаємо елемент, до якого прив'язуємо хук return <div ref={ref}>Click outside me!</div>; }
Тепер давайте перейдемо до його реалізацїї, перше що нам потрібно описати це пропси:
export interface Props { /** * Колбек-функція, яка викликається при детекції кліку за межами або натискання вказаної клавіші. */ onTriggered: (e: Event) => void; /** * Якщо true, вимикає детекцію кліків. * @за замовчуванням false */ disableClick?: boolean; /** * Якщо true, вимикає детекцію дотиків. * @за замовчуванням false */ disableTouch?: boolean; /** * Якщо true, вимикає детекцію натискання клавіші. * @за замовчуванням false */ disableKeys?: boolean; /** * Якщо true, викликає колбек на будь-яке натискання клавіші. * @за замовчуванням false */ allowAnyKey?: boolean; /** * Масив конкретних клавіш, які повинні викликати колбек. * @за замовчуванням [] */ triggerKeys?: string[]; }
З пропсами наче все зрозуміло, тепер давайте напишиме наш хук:
export function useDetectClickOutside({ onTriggered, disableClick = false, disableTouch = false, disableKeys = false, allowAnyKey = false, triggerKeys = [], }: Props): React.RefObject<HTMLElement> { /** * useRef<HTMLElement | null>(null) використовується для створення посилання на DOM-елемент. * Це посилання дозволяє отримати доступ до конкретного елемента в компоненті, щоб виконати перевірку, чи був клік або дотик за межами цього елемента. */ const ref = useRef<HTMLElement | null>(null); /** * Обробляє події натискання клавіші та викликає колбек, якщо умови виконуються. * useCallback використовується для мемоізації функцій в React. * Це дозволяє зберігати одну і ту ж функцію між рендерами, що запобігає її перевизначенню при кожному рендері компонента. */ const handleKeyEvent = useCallback((e: KeyboardEvent) => { if (allowAnyKey || triggerKeys.includes(e.key) || e.key === 'Escape') { onTriggered(e); } }, [allowAnyKey, triggerKeys, onTriggered]); /** * Обробляє події кліку або дотику та викликає колбек, якщо подія сталася поза елементом, на який є посилання. */ const handleClickOrTouch = useCallback((e: MouseEvent | TouchEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) { onTriggered(e); } }, [onTriggered]); /** * useEffect — це хук, який дозволяє виконувати побічні ефекти в функціональних компонентах React. * Виконується після рендеру компонента, що дозволяє працювати з результатами рендеру чи змінювати стан компонента. */ useEffect(() => { // Додаємо слухачів подій if (!disableClick) document.addEventListener('click', handleClickOrTouch); if (!disableTouch) document.addEventListener('touchstart', handleClickOrTouch); if (!disableKeys) document.addEventListener('keyup', handleKeyEvent); // Функція для очищення слухачів подій return () => { if (!disableClick) document.removeEventListener('click', handleClickOrTouch); if (!disableTouch) document.removeEventListener('touchstart', handleClickOrTouch); if (!disableKeys) document.removeEventListener('keyup', handleKeyEvent); }; }, [disableClick, disableTouch, disableKeys, handleClickOrTouch, handleKeyEvent]); return ref; }
Вітаю, тепер у вас є все необхідне для трекінгу кліків за межами елемента в React.
1 коментар
Додати коментар Підписатись на коментаріВідписатись від коментарів