javascript 41 lines · 8 steps

Closing a dropdown on outside click in React

A dropdown component that listens for outside clicks and the Escape key to close itself, cleaning up its listeners as it goes.

Explained by highlit
1import { useEffect, useRef, useState } from "react";
2 
3export function Dropdown({ label, children }) {
4 const [open, setOpen] = useState(false);
5 const containerRef = useRef(null);
6 
7 useEffect(() => {
8 if (!open) return;
9 
10 function handlePointerDown(event) {
11 if (containerRef.current && !containerRef.current.contains(event.target)) {
12 setOpen(false);
13 }
14 }
15 
16 function handleKeyDown(event) {
17 if (event.key === "Escape") setOpen(false);
18 }
19 
20 document.addEventListener("mousedown", handlePointerDown);
21 document.addEventListener("keydown", handleKeyDown);
22 
23 return () => {
24 document.removeEventListener("mousedown", handlePointerDown);
25 document.removeEventListener("keydown", handleKeyDown);
26 };
27 }, [open]);
28 
29 return (
30 <div className="dropdown" ref={containerRef}>
31 <button
32 type="button"
33 aria-expanded={open}
34 onClick={() => setOpen((prev) => !prev)}
35 >
36 {label}
37 </button>
38 {open && <div className="dropdown__menu" role="menu">{children}</div>}
39 </div>
40 );
41}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Attaching document-level listeners inside useEffect and returning a cleanup function prevents duplicate handlers and memory leaks.
  2. 2A ref lets you test whether a click landed outside the component by comparing against the DOM subtree with contains.
  3. 3Gating the effect on open means listeners only exist while they're needed, and re-run whenever that state flips.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Closing a dropdown on outside click in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code