Як реалізувати 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.

2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів