😎

リンク内リンクのクリックが親要素に伝播するのを防ぐ

に公開

この記事を書こうと思ったきっかけ

Team DELTA の三浦です。
フロントエンドエンジニアとして、ShopifyやNext.jsで開発をしています。

今回、メニューの「コピーを作成」ボタンをクリックしたときに、メッセージの新規作成画面に遷移するはずが遷移しないという問題に遭遇しました。

アドレスバーを確認すると一度は新規作成画面に遷移しているようでしたが、すぐに編集画面に戻ってしまう現象が発生していました。

この問題を解決するために React のイベント伝播(イベントバブリング)について調査し、修正した内容をまとめました。同じような問題で悩んでいる方の参考になれば幸いです。

どんな人に読んでほしいか

  • React でイベント処理を実装している方
  • クリックイベントの伝播で思わぬ動作が発生して困っている方
  • Material-UI の Menu コンポーネントを使用している方

React のイベント伝播とは

React では、DOM と同様にイベントは親要素に伝播(バブリング)します。つまり、子要素でクリックイベントが発生すると、そのイベントは親要素にも伝わっていきます。

例えば、以下のような構造があるとします:

<Parent onClick={handleParentClick}>
  <Child onClick={handleChildClick} />
</Parent>

この場合、Child をクリックすると:

  1. まず handleChildClick が実行される
  2. その後、イベントが親に伝播して handleParentClick も実行される

これは通常は便利な機能ですが、時に予期しない動作を引き起こすことがあります。

今回の問題

今回の問題は、Material-UI の Menu コンポーネントと MenuItem コンポーネントを使用した際に発生しました。

<Menu open={!!anchorEl} anchorEl={anchorEl} onClose={() => setAnchorEl(null)}>
  {actions.map((a, key) => (
    <MenuItem key={key} onClick={ev => onAction?.(a.id)}>
      {a.label}
    </MenuItem>
  ))}
</Menu>

「コピーを作成」というメニュー項目をクリックすると、onAction ハンドラーが実行され、新規作成画面に遷移するはずでした。しかし、クリックイベントが親要素にも伝播してしまい、新規作成画面に遷移した後すぐに編集画面に戻ってしまう問題が発生していました。

アドレスバーを確認すると、一度は新規作成画面のURLに変わっていましたが、親要素のクリックイベントが処理されることで編集画面に戻ってしまっていたのです。

解決策:stopPropagation() を使う

この問題を解決するには、クリックイベントが親要素に伝播しないようにする必要があります。React では、イベントオブジェクトの stopPropagation() メソッドを使用することで、イベントの伝播を止めることができます。

修正前のコード:

<MenuItem key={key} onClick={ev => onAction?.(a.id)}>
  {a.label}
</MenuItem>

修正後のコード:

<MenuItem key={key} onClick={ev => {
  ev.stopPropagation();
  onAction?.(a.id);
}}>
  {a.label}
</MenuItem>

この修正により、MenuItem がクリックされたときに:

  1. ev.stopPropagation() でイベントの伝播を停止
  2. その後 onAction?.(a.id) を実行

という流れになり、親要素にクリックイベントが伝播しなくなります。

React でのイベント伝播制御の重要性

今回の問題は、一見シンプルな修正で解決できましたが、イベント伝播の理解が不足していると見つけるのが難しいバグになりがちです。特に以下のような場合は注意が必要です:

  1. ネストされたクリック可能な要素がある場合
  2. モーダルやドロップダウンなど、オーバーレイ要素を使用している場合
  3. 複数のイベントハンドラーが連鎖的に実行される可能性がある場合

React アプリケーションを開発する際は、イベントの伝播を意識して、必要に応じて stopPropagation() を使用することが重要です。

他のイベント伝播制御方法

stopPropagation() 以外にも、イベント伝播を制御するための方法があります:

  • preventDefault(): デフォルトのブラウザ動作(リンクのクリックによるページ遷移など)を防ぐ
  • return false: jQuery では伝播停止とデフォルト動作の防止を同時に行えますが、React では機能しません
  • イベントキャプチャフェーズの利用: より高度なケースで使用

まとめ

今回は、Material-UI の MenuItem コンポーネントでクリックイベントが親要素に伝播してしまう問題と、その解決策について紹介しました。

問題の原因は:

  • MenuItem のクリックイベントが親要素に伝播していた
  • 親要素のイベントハンドラーが実行されることで、意図しない画面遷移が発生していた

解決策は:

  • ev.stopPropagation() を使用してイベントの伝播を停止する

React でのイベント処理を実装する際は、イベントの伝播を常に意識し、必要に応じて適切に制御することが重要です。特に複雑なUIコンポーネントを扱う場合は、予期しない動作を防ぐために、イベント伝播の制御が必要になることが多いです。

この記事が、同様の問題で悩んでいる方の参考になれば幸いです。

DELTAテックブログ

Discussion