DEV Community

Cover image for Stop Copy-Pasting That "Click Outside" Snippet — Use This Hook Instead
Thiyagu Arunachalam
Thiyagu Arunachalam

Posted on

Stop Copy-Pasting That "Click Outside" Snippet — Use This Hook Instead

Every React developer has written this code at least once:

useEffect(() => {
  function handleClickOutside(event) {
    if (ref.current && !ref.current.contains(event.target)) {
      setOpen(false);
    }
  }
  document.addEventListener("mousedown", handleClickOutside);
  return () => document.removeEventListener("mousedown", handleClickOutside);
}, [ref]);

Enter fullscreen mode Exit fullscreen mode

You paste it from Stack Overflow. It works. You move on. Then you write it again next week for the modal. Then again, for the dropdown. Then again, for the tooltip.

Press enter or click to view image in full size

Photo by Lautaro Andreani on Unsplash
Sound familiar?

That’s exactly why I built @kitsunechaos/use-outside-click.

The Problem
Detecting a click outside an element is one of the most common UI patterns in React:

  • Closing a dropdown when you click elsewhere
  • Dismissing a modal on backdrop click
  • Collapsing a popover, tooltip, or color picker
  • Hiding a mobile navigation drawer
  • Yet every time, we write the same boilerplate — or worse, reach for a 6KB library that wraps it in a class component HOC from 2018.

Introducing @kitsunechaos/use-outside-click
A dead-simple React hook that detects clicks outside any element. That’s it. No fluff.

npm install @kitsunechaos/use-outside-click
Usage

import { useRef } from "react";
import useOutsideClick from "@kitsunechaos/use-outside-click";
function Dropdown() {
  const ref = useRef(null);
  const [open, setOpen] = useState(false);
  useOutsideClick(ref, () => setOpen(false));
  return (
    <div ref={ref}>
      <button onClick={() => setOpen(true)}>Open</button>
      {open && <ul>{/* dropdown items */}</ul>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

One import. One ref. One callback. Done.

What Makes It Different
Zero Dependencies
No bloat. No peer dependency warnings. Nothing extra in your node_modules.

Fully Typed
Written in TypeScript from the ground up. Full type inference — no @types/ package needed.

useOutsideClick(ref: RefObject, callback: () => void): void
SSR Safe
Works seamlessly with Next.js, Remix, and any server-side rendering setup. No window is defined.

Touch + Mouse Support
Handles both mousedown and touchstart events — works perfectly on mobile devices too.

Tiny Bundle Size
Under 400 bytes gzipped. Your users will never notice it’s there.

Supports Multiple Refs
Need to track outside clicks across multiple elements? Supported out of the box.

Real World Example — Dropdown Menu

import { useRef, useState } from "react";
import useOutsideClick from "@kitsunechaos/use-outside-click";
export function DropdownMenu() {
  const ref = useRef<HTMLDivElement>(null);
  const [isOpen, setIsOpen] = useState(false);
  useOutsideClick(ref, () => setIsOpen(false));
  return (
    <div ref={ref} style={{ position: "relative", display: "inline-block" }}>
      <button onClick={() => setIsOpen((prev) => !prev)}>
        Options ▾
      </button>
      {isOpen && (
        <ul style={{ position: "absolute", background: "white", border: "1px solid #eee" }}>
          <li>Edit</li>
          <li>Duplicate</li>
          <li>Delete</li>
        </ul>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

No more cleanup logic. No more forgotten removeEventListener. Just clean, readable code.

Before vs After**
Before — raw boilerplate every time:**

// 10+ lines, every single component
useEffect(() => {
  function handleClickOutside(event) {
    if (ref.current && !ref.current.contains(event.target)) {
      onClose();
    }
  }
  document.addEventListener("mousedown", handleClickOutside);
  return () => {
    document.removeEventListener("mousedown", handleClickOutside);
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

After — one line:

useOutsideClick(ref, onClose);
Enter fullscreen mode Exit fullscreen mode

Installation

npm

npm install @kitsunechaos/use-outside-click
Enter fullscreen mode Exit fullscreen mode

yarn

yarn add @kitsunechaos/use-outside-click
Enter fullscreen mode Exit fullscreen mode

pnpm

pnpm add @kitsunechaos/use-outside-click
Enter fullscreen mode Exit fullscreen mode

Links
📦 npm → npmjs.com/package/@kitsunechaos/use-outside-click
🐙 GitHub → github.com/kitsunechaos-labs/use-outside-click
If this saved you from writing that boilerplate one more time — drop a ⭐ on GitHub. It genuinely helps other developers discover it.

Built with 🦊 by kitsunechaos

Top comments (0)