I’m starting a series where I write my experience using concepts. I’m doing this because most times we hear about new or special concepts with the tools we use but never get to use them or fully understand them. Mostly, I’m doing this to gain mastery of the tools I work with (HTML, CSS, React) so disclaimer, this is not expert opinion.
I came across a problem recently where I was going to copy/paste logic across different components. The components were a dropdown and an input element, I needed to be able to
- Close the dropdown when I clicked anywhere outside it and
- Hide the input field when I clicked anywhere outside it
So, the common denominator was “when I clicked outside the element”. There’s a package for this, look out for a link in the last section. I also found an easy solution and had it working for me on the dropdown element. To avoid repeating logic too many times, I thought about using a custom hook.
Snippet from the dropdown component
const selectRef = useRef(null);
useEffect(() => {
function handleClickOutside(event) {
if (selectRef.current && !selectRef.current.contains(event.target)) {
close();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [selectRef]);
return (<div ref={selectRef}></div>)
Essentially, I’m adding an event listener that listens to the mousedown event and carries out the handleClickOutside function. The function checks if the clicked element is the element with the specified ref and if not, it calls a function close()
that closes the dropdown in the div.
A custom hook helps to extract reusable logic in to a function. Extracting the logic to a hook
import { useState, useEffect } from "react";
export function useClickedOut(ref) {
const [active, setActive] = useState(false);
const onClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setActive(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", onClickOutside);
return () => {
document.removeEventListener("mousedown", onClickOutside);
};
});
return [active, setActive];
};
Here, I have a state active
that is set to false anytime a user clicks outside the target ref.
Using the hook in my dropdown component
const Dropdown = () => {
const targetRef = useRef(null);
const [ menuOpen, setMenuOpen ] = useClickOutside(targetRef);
const onMenuBtnClick = () => {
setMenuOpen(!menuOpen);
};
return (
<div ref={targetRef} className="dropdown-wrapper">
<button className="openMenu" type="button" onClick={onMenuBtnClick}>
Open Dropdown Menu
</button>
{menuOpen && (
<div className="dropdown-items-wrapper">
<h3>Menu Items</h3>
<ul className="dropdown-items">
<li className="dropdown-item">Dropdown Item 1</li>
<li className="dropdown-item">Dropdown Item 2</li>
<li className="dropdown-item">Dropdown Item 3</li>
</ul>
</div>
)}
</div>
);
};
Now, I can use this hook wherever I need to act after a user clicks outside a particular element.
I’ve also put a full illustration here
I hope you enjoyed reading this, feel free to comment on what you think or what you’ve tried or, how you would have solved this.
References and further reading
Using a Custom Hook
Detect click outside React component
React Click Outside
Top comments (0)