私が歌川です

@utgwkk が書いている

イベントハンドラを設定した要素内にreact-modalのモーダルを置くとイベントハンドラが反応する

tl;dr

イベントハンドラを設定した要素内にreact-modalのモーダルを置かないようにするのが手っ取り早そう。

イベントハンドラを設定したコンポーネント

以下の Clickable コンポーネントは、divに click イベントのハンドラを設定しており、かつchildrenを取るコンポーネントである。divをクリックするとコンソールにログを出力する。

import { FC, MouseEventHandler, useCallback } from "react";

export const Clickable: FC = ({ children }) => {
  const handleMouseDown: MouseEventHandler = useCallback((e) => {
    console.log(`clicked!!!! ${new Date()}`);
  }, []);

  return (
    <div onMouseDown={handleMouseDown}>
      {children}
    </div>
  );
};

モーダル、外に置くか? 中に置くか?

react-modalのモーダルを <Clickable> の外に置くか、それとも中に置くか、について考える。

外に置く

モーダルを <Clickable> の外に置くとこういう感じになる。モーダルを開くボタンだけが <Clickable> の中にある。とくに気にかけることもない。

<div>
  <h2>along with modal</h2>
  <Clickable>
    This area is clickable!!!!!
    <p>
      <button onClick={() => setIsOpen(true)}>open modal</button>
    </p>
  </Clickable>
  <Modal isOpen={isOpen} onRequestClose={handleRequestClose}>
    <div>
      <h2>Modal</h2>
      <button onClick={handleRequestClose}>close</button>
    </div>
  </Modal>
</div>

中に置く

一方で、モーダルを <Clickable> の中に置くとどうなるか? この場合もモーダルを開くことはできるし、モーダルが前面に出てくるのだが、モーダル内をクリックすると <Clickable> のイベントハンドラが反応してしまう。これは多くの場合は望ましい挙動ではないと思う。

<div>
  <h2>contains modal</h2>
  <Clickable>
    This area is clickable!!!!!
    <p>
      <button onClick={() => setIsOpen(true)}>open modal</button>
    </p>
    <Modal isOpen={isOpen} onRequestClose={handleRequestClose}>
      <div>
        <h2>Modal</h2>
        <button onClick={handleRequestClose}>close</button>
      </div>
    </Modal>
  </Clickable>
</div>

どうするか

モーダル内で、モーダルを設置した要素の親のイベントハンドラが呼ばれてほしくないなら、モーダルを外に出したほうがよい。

感想

普通はイベントハンドラを設定した要素の子にモーダルを設置することはないかもしれないけど、モーダルを動的に生成する場合に油断すると引っかかると思う。実際にこの現象に遭遇して、DOM treeの上ではreact-modalのモーダルは完全にroot要素の外にあるけど、Reactが管理するtreeの上では必ずしもそうなっていない*1のがデバッグを難しくしていた。CSSの pointer-events プロパティで解決できるかもしれない、と思っていろいろ試してみたけど解決できなかった。

サンプルコード

github.com

*1:React Developer Toolsで確認できる