EN
React - detect click outside component hook (works with nested elements)
10
points
In this short article, we would like to show how in React write a custom hook that handles clicks outside component.
Note: presented in this article solution is able to handle outside clicks even clicked component contains nested elements.
Quick solution:
const useOutideClickHandler = onOutsideClick => {
const ref = React.useRef(null);
React.useEffect(() => {
const handleWindowClick = e => {
if (onOutsideClick) {
let node = e.target;
while (node) {
if (ref.current === node) {
return;
}
node = node.parentNode;
}
onOutsideClick();
}
};
window.addEventListener('click', handleWindowClick);
return () => {
window.removeEventListener('click', handleWindowClick);
};
}, [onOutsideClick]);
return ref;
};
// Usage example:
const elementRef = useOutideClickHandler(() => console.log('Outside clicked!'));
<div>
<div ref={elementRef}>Content here ...</div>;
Outside area ...
</div>
Practical example
To avoid unnecessary function recreation useOutideClickHandler()
body was moved to addOutideClickListener()
function.
// ONLINE-RUNNER:browser;
// import React from 'react';
// import ReactDOM from 'react-dom';
const addOutideClickListener = (ref, onOutsideClick) => {
const handleWindowClick = e => {
if (onOutsideClick) {
let node = e.target;
while (node) {
if (ref.current === node) {
return;
}
node = node.parentNode;
}
onOutsideClick();
}
};
window.addEventListener('click', handleWindowClick);
return (): void => {
window.removeEventListener('click', handleWindowClick);
};
};
const useOutideClickHandler = onOutsideClick => {
const ref = React.useRef(null);
React.useEffect(() => addOutideClickListener(ref, onOutsideClick), [onOutsideClick]);
return ref;
};
// Usage example:
const App = () => {
const buttonRef = useOutideClickHandler(() => console.log('Outside button cliked!'));
const divRef = useOutideClickHandler(() => console.log('Outside div cliked!'));
return (
<div>
<button ref={buttonRef}>Click me!</button>
<br /><br />
<div ref={divRef} style={{padding: '10px', background: '#02d456'}}>
Click me!
<div style={{padding: '10px', background: '#feeba4'}}>
Click me too! - it works when elements are nested too!
</div>
</div>
</div>
);
};
const root = document.querySelector('#root');
ReactDOM.render(<App />, root);