useEffectでaddEventListenerしたのに、クリック処理が何度も発火する話
Reactでウィンドウのリサイズやスクロールを監視する処理をuseEffectで書いたとき、「同じクリックで処理が2回、3回と実行されるようになった」という症状に出会うことがあります。今回はこの原因と直し方を整理します。
Before: クリーンアップを忘れたコード
function useOutsideClick(onOutsideClick: () => void) {
useEffect(() => {
function handleClick(e: MouseEvent) {
onOutsideClick();
}
window.addEventListener("click", handleClick);
}, [onOutsideClick]);
}
「要素の外側をクリックしたらモーダルを閉じる」のような処理でよく書かれるパターンです。一見問題なさそうですが、これを使うコンポーネントが再マウントされるたびに、addEventListenerだけが積み重なっていきます。
なぜ発火回数が増えていくのか
useEffectの中でaddEventListenerを呼ぶと、そのたびに新しいリスナーが登録されます。コンポーネントがアンマウントされる時に対応するremoveEventListenerを呼んでいないと、古いリスナーは削除されずに残ったままになります。
たとえばモーダルを開閉するたびにこのフックが動くコンポーネントがマウント・アンマウントを繰り返すと、リスナーの数はどんどん増えていきます。1回のクリックで、残っている全部のリスナーが反応するので、「クリックのたびに実行回数が増えていく」という現象になります。
エラーは出ないので、開発中に気づきにくいのも厄介な点です。
なお、コンポーネントがアンマウントされていなくても、依存配列に渡している値(この例ではonOutsideClick)が親から毎回新しい関数として渡されている場合、依存配列が変わったと判定されてuseEffectが再実行されます。この時もクリーンアップがなければ、古いリスナーが残ったまま新しいリスナーが追加されるので、同じ現象が起きます。
After: クリーンアップ関数で解除する
function useOutsideClick(onOutsideClick: () => void) {
useEffect(() => {
function handleClick(e: MouseEvent) {
onOutsideClick();
}
window.addEventListener("click", handleClick);
return () => {
window.removeEventListener("click", handleClick);
};
}, [onOutsideClick]);
}
useEffectの中で返す関数(クリーンアップ関数)は、次にこのuseEffectが実行される直前と、コンポーネントがアンマウントされる時に呼ばれます。ここでremoveEventListenerを呼んでおくことで、リスナーが積み重なるのを防げます。
addEventListenerを書いたら、その場でセットでremoveEventListenerも書いてしまうくらいの感覚でいると、この手の見落としを減らせます。
見落としに気づくためのチェック方法
ブラウザの開発者ツールを使うと、実際にリスナーが増えているかを確認できます。
Elements タブ → html要素を選択 → Event Listeners タブ
windowに登録したリスナーは、html要素を選んだ状態で確認できます(「Ancestors」のチェックが入っているのが初期設定です)。同じイベント(clickなど)に対して、複数のハンドラが登録されていたら、クリーンアップ漏れのサインです。
AIへの確認の仕方
「クリック処理が複数回実行される」という症状だけでは、AIも原因を絞り込みにくいことがあります。次のように、関連するコードとセットで伝えるとスムーズです。
ボタンをクリックすると、処理が複数回実行されてしまいます。
useEffectでaddEventListenerしている箇所のコードを貼ります。
クリーンアップ関数でremoveEventListenerを
呼び忘れていないか確認してください。
(ここにuseEffect部分のコードを貼る)
「何度も実行される」という症状から、クリーンアップ漏れを連想するのは初見だと難しいので、原因の候補を伝えた上で確認してもらうのが近道です。
未経験者向けの講座を運営しています
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全24セッションで体系化した教材です。Claude Code を学習パートナーにする CLAUDE.md と学習モード(learner / developer)の設計までセットで含みます。
- ▶ ライブデモ(インストール不要・無料で今すぐ見られます)→ https://trial-web-ayies128s-projects.vercel.app/?ref=qiita
- 教材完全版+月5,500円のメンタリング(全24セッション+チャット質問し放題)→ https://menta.work/plan/20251?ref=qiita
- 無料体験版(git clone して自分の手元で動かす・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- YouTube『AIエンジニア情報局』(AI×開発ニュースを1本5分でキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita
※ Qiita 読者の方には易しすぎる内容なので、初心者の知り合いへの紹介や社内研修の参考としてどうぞ。