👋

なぜかクリックイベントが暴発する?React+MUIのTableRowでハマった話

2025/04/08に公開2

なぜかクリックイベントが暴発する?React+MUIのTableRowでハマった話

削除ボタン押したのに、次に別の場所クリックするとモーダルが開く……。その違和感、ちゃんと対処できます。

「TableRowが勝手に反応する」現象、起きてませんか?

React × MUI を使ってテーブルを作っていて、こんな経験ありませんか?

  • TableRowをクリックするとモーダルが開くようにしている
  • でも、行内にあるボタン(削除など)を押したあと、他の場所をクリックするとモーダルが勝手に開く
<TableRow onClick={() => handleRowClick(row)}>
  <TableCell>{row.name}</TableCell>
  <TableCell>
    <DeleteButton onClick={(e) => { e.stopPropagation(); handleDelete(row); }} />
  </TableCell>
</TableRow>

event.stopPropagation()してるのに、なぜ?」と思った方。僕もそうでした。この記事では、 なぜその現象が起きるのか? と、 どう防げばいいのか? をまとめます。


なぜ stopPropagation() だけでは不十分なのか

ReactはDOMのイベントを"合成イベント(SyntheticEvent)"という仕組みで扱っています。

この合成イベント、以下のような特徴があります:

  • イベントは一度プールされ、必要に応じて遅延的に処理されることがある
  • MUIなどのコンポーネントライブラリも内部で色々やってる

結果として、「その時止めたつもりでも、React的には“あとで効いてくる”」ということが発生します。

これが “削除ボタン押したのに、その後でモーダルが開く” 原因です。


解決法:TableRowのonClickにフィルタを入れる

target.closest('td') を使って「TableCell内クリックのみ許可」

<TableRow
  onClick={(event) => {
    const target = event.target as HTMLElement;

    // TableCell(td)内のクリックだけ許可
    if (!target.closest('td')) return;

    handleRowClick(row);
  }}
>

closest('td') は、クリックされた要素から親をたどって最初の<td>を探します。

  • つまり「TableCell内をクリックしたとき」だけ、モーダルを開く
  • ボタン・アイコンなどで<td>の外にある場合はスルーできる

✅ TableRow全体ではなく「空白部分のみ」反応させたい場合

<TableRow
  onClick={(event) => {
    if (event.target !== event.currentTarget) return;
    handleRowClick(row);
  }}
>

この方法では、TableRowの“純粋な背景部分”しか反応しません。好みに合わせて使い分けましょう。


なぜこの対策が効くのか?

closest() はセレクタベースの親探索

クリックされた要素(たとえば<svg>)が<td>もしくは<td>内にあれば、closest('td')でちゃんとマッチしてくれます。

event.target !== event.currentTarget

これは、クリックされた要素がRowそのものだったか?を判定するシンプルな方法です。


最後に:この記事で伝えたいこと

  • MUI + Reactは便利だけど、ちょっとしたUXでハマりポイントがある
  • stopPropagation()では不十分なケースがある(SyntheticEventの罠)
  • closest('td')event.currentTargetの知識があると、スマートに防げる

「TableRowのonClick、なぜか暴発する」問題で困っていた方が、この記事でスッキリできたら嬉しいです。


Discussion

junerjuner

クリックされた要素(たとえば<svg>)が<td>内にあれば、closest('td')でちゃんとマッチしてくれます。

この場合は クリックされた要素が <td> もしくは <td>内にあれば、 ではないでしょうか……?

いんざいいんざい

そうですね💦
ご指摘ありがとうございます!
修正いたしました!