はじめに
Reactを使っている人は、たぶん一度はこのコードを書いたことがあると思います。
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [ref]);
Stack Overflowからコピーして、動いたから次に進む。
来週、モーダルのために同じコードをまた書く。
その次の週、ドロップダウンのために。
また次の週、ツールチップのために。
覚えがありますか?
だから、@kitsunechaos/use-outside-click を作りました。
問題
「要素の外をクリックしたか」を検知するのは、よく使うUIのパターンです:
- ドロップダウンを閉じる
- モーダルの外をクリックしたら閉じる
- ポップオーバーやツールチップを隠す
- モバイルのナビゲーションを閉じる
でも毎回、同じボイラープレートを書いています。
または、2018年に作られた古い6KBのライブラリを使っています。
もっといい方法があるはずです。
紹介:@kitsunechaos/use-outside-click
シンプルなReact Hookです。要素の外のクリックを検知します。それだけです。
npm install @kitsunechaos/use-outside-click
使い方
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>{/* ドロップダウンのアイテム */}</ul>}
</div>
);
}
importは1つ。refは1つ。callbackは1つ。終わり。
このHookの特徴
✅ 依存関係ゼロ
余分なパッケージはありません。node_modulesが増えません。
✅ TypeScript対応
最初からTypeScriptで書いています。型推論が完全に動きます。@types/は不要です。
useOutsideClick(ref: RefObject<HTMLElement>, callback: () => void): void
✅ SSR対応
Next.jsやRemixでも問題なく動きます。window is not definedエラーは出ません。
✅ タッチ・マウス両対応
mousedownとtouchstartの両方に対応しています。スマホでも動きます。
✅ サイズが小さい
gzip後、400バイト以下です。ユーザーは気づきません。
✅ 複数のRefに対応
複数の要素を同時に監視できます。
実際の例:ドロップダウンメニュー
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>
);
}
removeEventListenerの書き忘れはもうありません。コードがきれいになります。
Before / After
Before(毎回このボイラープレート):
// 10行以上、毎回書く
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
onClose();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
After(1行):
useOutsideClick(ref, onClose);
インストール
# npm
npm install @kitsunechaos/use-outside-click
# yarn
yarn add @kitsunechaos/use-outside-click
# pnpm
pnpm add @kitsunechaos/use-outside-click
リンク
- 📦 npm → npmjs.com/package/@kitsunechaos/use-outside-click
- 🐙 GitHub → github.com/kitsunechaos-labs/use-outside-click
このHookが役に立ったら、GitHubに ⭐ をつけてもらえると嬉しいです。他の開発者が見つけやすくなります。