Skip to main content

Command Palette

Search for a command to run...

How I Handle Outside Clicks In My Applications

A lightweight, flexible React component for handling outside clicks, with support for multiple exception zones and no third-party libraries.

Updated
3 min read
How I Handle Outside Clicks In My Applications

From dropdown menus to modals and popups, the "click away" pattern isn’t just widely used, it’s expected in most modern user interfaces. Why bother clicking an “x” when you've got the whole screen to dismiss something for you?

Common Approaches

If you’re using vanilla JavaScript and HTML, your solution might look like this:

<div id="dropdown">Dropdown Content</div>

<script>
  const dropdown = document.getElementById('dropdown');

  document.addEventListener('click', function (event) {
    if (!dropdown.contains(event.target)) {
      // Handle outside click
      dropdown.style.display = 'none';
    }
  });
</script>

In a React environment, you’d likely do something like this:

useEffect(() => {
  function handleClick(e) {
    if (ref.current && !ref.current.contains(e.target)) {
      onOutsideClick();
    }
  }

  document.addEventListener("mousedown", handleClick);
  return () => document.removeEventListener("mousedown", handleClick);
}, []);

Maybe wrap it in a custom hook:

function useOutsideClick(ref, callback) {
  useEffect(() => {
    function handleClick(e) {
      if (ref.current && !ref.current.contains(e.target)) {
        callback();
      }
    }

    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [ref, callback]);
}

Why I Didn’t Use a Library

Now, you might ask: couldn't I just use something like useClickAway from react-use or useOutsideClick from Headless UI? Absolutely!

While my version is slightly optimized for my needs by accepting multiple exception refs, the true as to why I built it from scratch is... because i felt like it.

I usually favor the DIY route, I knew it wouldn’t take much time, and figured it might even be instructive. Luckily, these assumptions ended up being correct, something that doesn’t happen as often as I would like.

Implementation

Usage

Wraps any content and triggers a callback (onOutsideClick) when the user clicks anywhere outside of it, unless the click happens in an exception zone.

<OutsideClickHandler 
    onOutsideClick={() => console.log("Clicked outside!")}
    exceptionRefs={[exceptionRef]}
>
    {/* content */}
</OutsideClickHandler>

Props

children (required): The content you want to protect or track clicks outside of.

onOutsideClick (required): A function that fires when the user clicks outside the main element (and any exceptions).

exceptionRefs (optional, default = []): An array of refs to other elements you want to ignore in the outside click logic (like tooltips, buttons, floating menus).

isActive (optional, default = true): A boolean to enable or disable the behavior dynamically.

useEffect

  1. Attaches a mousedown listener to the document on mount.

  2. On every click, checks:

    • Whether the click is outside the main ref.

    • And outside all exception refs (.every()).

  3. If both conditions are met, calls onOutsideClick().

  4. Cleans up the listener on unmount or when isActive changes, avoiding memory leaks or duplicate handlers.

Design Advantages

  • Encapsulated: Keeps outside-click logic self-contained and clean.

  • Flexible: Supports multiple exception zones (unlike most hooks).

  • Declarative: Feels like wrapping behavior around content, rather than injecting logic.

Demo

Source code

The full code is available on GitHub.