【React】特定の要素の外側をクリックしたときのイベントの設定方法

モーダルやポップアップなどで外側をクリックしたときに閉じる実装をしたいと思ったことはありますでしょうか?

今回は特定の要素の外側をクリックしたときのイベントの設定方法を紹介します。

目次

まずはサンプルを用意したので確認してみてください

緑色のエリア内もしくはエリア外をクリックすると、その下にテキストでクリックした場所が表示されます。

それでは実装方法を見ていきます

下記のコードは先ほど見ていただいたサンプルの重要な部分だけを抜き出したコードになります。

import { useEffect, useRef } from "react";

const Home = () => {
  const insideRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    //対象の要素を取得
    const el = insideRef.current;

    //対象の要素がなければ何もしない
    if (!el) return;

    //クリックした時に実行する関数
    const hundleClickOutside = (e: MouseEvent) => {
      if (!el?.contains(e.target as Node)) {
        //ここに外側をクリックしたときの処理
      } else {
        //ここに内側をクリックしたときの処理
      }
    };

    //クリックイベントを設定
    document.addEventListener("click", hundleClickOutside);

    //クリーンアップ関数
    return () => {
      //コンポーネントがアンマウント、再レンダリングされたときにクリックイベントを削除
      document.removeEventListener("click", hundleClickOutside);
    };
  }, [insideRef]);

  return (
    <div className="wrapper">
      <div className="insideArea" ref={insideRef}>
        <p>このエリア内とエリア外をクリックしてみてください。</p>
      </div>
    </div>
  );
};

export default Home;

コメントでほとんど説明しているような感じですが、ここから順番に実装方法を見ていきます。

ここまでで十分!理解した!という方はここでブラウザバックしていただいてOKです(笑)

1, useRefで対象の要素を取得

import { useRef } from "react";

const Home = () => {
  const insideRef = useRef<HTMLDivElement>(null);
    ...

  return (
    <div className="wrapper">
      <div className="insideArea" ref={insideRef}>
        <p>このエリア内とエリア外をクリックしてみてください。</p>
      </div>
    </div>
  );
};

まずはuseRefでクリックしたときの内側にあたる要素を取得します。

今回の場合はdiv className="insideArea"の部分ですね。

2, useEffectで「内側にあたる要素」を取得できた場合にクリックイベントを設定

const Home = () => {
  const insideRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    //対象の要素を取得
    const el = insideRef.current;

    //対象の要素がなければ何もしない
    if (!el) return;

    //クリックした時に実行する関数
    const hundleClickOutside = (e: MouseEvent) => {
      if (!el?.contains(e.target as Node)) {
        //ここに外側をクリックしたときの処理
      } else {
        //ここに内側をクリックしたときの処理
      }
    };

    //クリックイベントを設定
    document.addEventListener("click", hundleClickOutside);

    ...

  }, [insideRef]);

  ...
};
  1. まずはuseEffectの第二引数に先ほどのuseRefで取得した要素を入れ監視します。
  2. useRefで要素の取得に成功した場合にクリックイベント(hundleClickOutside)をdocumentに設定します。

3, クリックイベントの関数について

//クリックした時に実行する関数
const hundleClickOutside = (e: MouseEvent) => {
  if (!el?.contains(e.target as Node)) {
    //ここに外側をクリックしたときの処理
  } else {
    //ここに内側をクリックしたときの処理
  }
};

この処理は単純でuseRefで取得した要素クリックした要素が同じかどうかをcontainsで判定しているだけです。

4, クリックイベントの削除

//クリーンアップ関数
return () => {
  //コンポーネントがアンマウント、再レンダリングされたときにクリックイベントを削除
  document.removeEventListener("click", hundleClickOutside);
};

クリックイベントの削除はuseEffectのクリーンアップ関数を使い削除します。

これをやっておかないとページ遷移したときなどに遷移先でイベントが残り続けてしまうからです。

まとめ

内容をまとめると以下の通りです。

  1. useRefで内側にあたる要素を取得
  2. documentにクリックイベントを設定
  3. クリック時にuseRefので取得した要素とe.targetを比較
  4. 同じでなければ外側をクリックしたということになる

最後に

長くなってしまいましたが、これで解説は終了になります。

実際に使用する場合は要件に合わせてクリックイベントの設定や削除タイミングなどを調整する必要があるかと思います。

参考記事

https://stackoverflow.com/questions/57515417/not-assignable-type-for-event-listener

JavaScript版はこちら