YASD-TECH
YASD TECH
# React

preventDefaultの使いどころ(stopPropagationとの違い)

投稿日:2026/8/8

更新日:2026/8/8

ttitleImage

preventDefault() の使いどころ(stopPropagation() との違い)

フォームの onSubmit でとりあえず書いている event.preventDefault() が、結局何を止めているのかを整理する。
よく出てくる場面のカタログと、混同しがちな stopPropagation() との違い、効かないケースまでまとめた。

結論

  • preventDefault()ブラウザが元々やろうとする動作(デフォルト動作)を止めるだけ。
  • 親要素への伝播は止まらない。それを止めたいのは stopPropagation() で、両者は別物。
  • 出番はほぼ決まっている → フォーム送信・リンク遷移・キーボードショートカット・ドラッグ&ドロップ・右クリックメニュー
  • cancelable: false のイベントや passive リスナーでは 何も起きないscroll や React の onWheel など)。

1. デフォルト動作とは何か

ブラウザは、JavaScript が何も書かれていなくても要素ごとに決まった動作をする。

イベント ブラウザのデフォルト動作
submit(form) フォームを送信してページを遷移/リロードする
click(a タグ) href 先へ遷移する
click(checkbox) チェック状態を切り替える
keydown(Cmd/Ctrl + S) 「ページを保存」ダイアログを開く
keydown(Space / 矢印キー) ページをスクロールする
contextmenu ブラウザ標準の右クリックメニューを出す
drop ドロップされたファイルをそのタブで開く

preventDefault() は、この「決まった動作」だけをキャンセルする。

一番多いのはフォーム

tsx
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault();

  // ページ遷移させずに API 送信する
  saveForm();
};

return <form onSubmit={handleSubmit}>{/* ... */}</form>;

preventDefault() が無いとどうなるかを時系列で見ると分かりやすい。

sequenceDiagram
    participant U as ユーザー
    participant B as ブラウザ
    participant H as onSubmit ハンドラ
    participant S as APIサーバー

    U->>B: 送信ボタンをクリック
    B->>H: submit イベント
    H->>H: preventDefault()
    H->>S: fetch("/api/orders")
    S-->>H: 200 OK
    H->>B: 画面を更新(遷移なし)
    Note over B: preventDefault() が無いと<br/>ここでページがリロードされ<br/>fetch が中断される

フォームでは preventDefault() を忘れると「一瞬 API が飛んだように見えるがページがリロードされる」という挙動になり、原因が分かりにくい。


2. よく出てくる場面カタログ

2-1. リンクの遷移を止めて確認ダイアログを出す

tsx
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
  if (!isDirty) return; // 未保存の変更が無ければ、そのまま普通に遷移させる

  event.preventDefault(); // 遷移を止める
  setShowConfirmDialog(true);
};

return <a href="/orders" onClick={handleClick}>注文一覧へ</a>;

条件付きで preventDefault() を呼ぶのがポイント。無条件に止めてしまうと、Cmd + クリックの別タブ表示なども潰れる。

2-2. Cmd + S / Ctrl + S をアプリ内保存に割り当てる

グローバルなキーイベントなので window に直接張る。

tsx
useEffect(() => {
  const handleKeyDown = (event: KeyboardEvent) => {
    if ((event.metaKey || event.ctrlKey) && event.key === 's') {
      event.preventDefault(); // ブラウザの「ページを保存」を止める
      save();
    }
  };

  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [save]);

2-3. Space / 矢印キーによるスクロールを止める

ゲームやエディタなど、キー入力を自前で扱う画面で必要になる。

tsx
const handleKeyDown = (event: React.KeyboardEvent) => {
  if (event.code === 'Space' || event.key.startsWith('Arrow')) {
    event.preventDefault(); // ページのスクロールを止める
  }
  moveCursor(event.key);
};

2-4. ドラッグ&ドロップを受け付ける

ここは「止めないと動かない」珍しいパターン。dragover のデフォルト動作を止めないと drop が発火しない。

tsx
<div
  onDragOver={(event) => event.preventDefault()} // これが無いと onDrop が呼ばれない
  onDrop={(event) => {
    event.preventDefault(); // ブラウザがファイルをそのタブで開くのを止める
    const files = Array.from(event.dataTransfer.files);
    upload(files);
  }}
>
  ここにファイルをドロップ
</div>

2-5. 右クリックメニューを独自メニューに差し替える

tsx
<div
  onContextMenu={(event) => {
    event.preventDefault(); // ブラウザ標準メニューを止める
    openMenu(event.clientX, event.clientY);
  }}
>

3. preventDefault()stopPropagation() は別物

一番の勘所。両者は イベントの流れの違うところを切っている

flowchart TD
    A["イベント発生"] --> B["キャプチャ<br/>window → 親 → ターゲット"]
    B --> C["ターゲットのハンドラ"]
    C --> D["バブリング<br/>親 → window"]
    D --> E["ブラウザのデフォルト動作<br/>(遷移・リロードなど)"]

    P["preventDefault()"] -. "止める" .-> E
    S["stopPropagation()"] -. "止める" .-> D
preventDefault() stopPropagation() stopImmediatePropagation()
止めるもの ブラウザのデフォルト動作 親要素へのイベント伝播 伝播 + 同じ要素の残りのハンドラ
親のハンドラは走る? 走る 走らない 走らない
デフォルト動作は起きる? 起きない 起きる 起きる

つまり、

  • モーダルの中のボタンで外側の「閉じる」ハンドラを走らせたくないstopPropagation()
  • リンクの遷移だけ止めたいpreventDefault()
  • 両方必要なら両方呼ぶ
tsx
const handleClick = (event: React.MouseEvent) => {
  event.preventDefault();  // 遷移を止める
  event.stopPropagation(); // 親のクリックハンドラを走らせない
};

React のハンドラで return false と書いても何も起きない。jQuery やインライン onclick では「preventDefault + stopPropagation」の意味だったが、React では単なる戻り値として捨てられる。


4. addEventListener と React の onXxx の使い分け

実務感でいうと、境界はだいたいこう分かれる。

使う場面
addEventListener React の外、window / document に張るグローバルなイベント(ショートカット、リサイズ、beforeunload
React の onXxx フォーム、リンク、要素内のドラッグ&ドロップ、右クリックなど特定の要素に紐づくイベント

React の onXxx で足りるならそちらを使う。addEventListener はクリーンアップ(removeEventListener)を自分で書く必要があり、書き忘れるとリスナーが積み上がる。


5. ハマりどころ

5-1. cancelable: false のイベントには効かない

scroll のように「もう起きてしまった結果」を通知するイベントは、そもそもキャンセルできない。

ts
element.addEventListener('scroll', (event) => {
  console.log(event.cancelable); // false
  event.preventDefault();        // 何も起きない
});

スクロールを止めたいなら、原因側の wheel / touchmove を止めるか、CSS の overflow: hidden を使う。

止まったかどうかは defaultPrevented で確認できる。

ts
event.preventDefault();
console.log(event.defaultPrevented); // true なら実際に止まっている

5-2. passive リスナーでは無視される

{ passive: true } は「このハンドラは preventDefault() しません」という宣言で、スクロール性能のために使われる。宣言に反して呼ぶと無視され、コンソールに警告が出る。

さらに厄介なのが、touchstart / touchmove / wheelwindow / document / body に張ると ブラウザが既定で passive 扱いにすること。止めたいなら明示する。

ts
window.addEventListener('touchmove', handleTouchMove, { passive: false }); // ✅ 効く

5-3. React の onWheel / onTouchMovepreventDefault() が効かない

React 17 以降、onWheel / onTouchStart / onTouchMovepassive リスナーとして登録される。React のハンドラ内で preventDefault() を書いても効かない。

ref から native リスナーを張り直すのが定石。

tsx
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
  const el = ref.current;
  if (!el) return;

  const handleWheel = (event: WheelEvent) => {
    event.preventDefault(); // ✅ passive: false なので効く
    zoom(event.deltaY);
  };

  el.addEventListener('wheel', handleWheel, { passive: false });
  return () => el.removeEventListener('wheel', handleWheel);
}, []);

return <div ref={ref}>{/* ... */}</div>;

5-4. <form> の中の <button> は既定で type="submit"

「ただの操作ボタンのつもりが、押すとページがリロードされる」の原因はほぼこれ。

tsx
<button type="button" onClick={addRow}>行を追加</button> {/* ✅ type を明示する */}

なお、送信自体は type="button" で回避せず、onSubmit + preventDefault() を基本にする。Enter キーでの送信やブラウザ標準のバリデーションが効かなくなるため。


参考

Index

  • preventDefault() の使いどころ(stopPropagation() との違い)
  • 結論
  • 1. デフォルト動作とは何か
  • 一番多いのはフォーム
  • 2. よく出てくる場面カタログ
  • 2-1. リンクの遷移を止めて確認ダイアログを出す
  • 2-2. Cmd + S / Ctrl + S をアプリ内保存に割り当てる
  • 2-3. Space / 矢印キーによるスクロールを止める
  • 2-4. ドラッグ&ドロップを受け付ける
  • 2-5. 右クリックメニューを独自メニューに差し替える
  • 3. preventDefault() と stopPropagation() は別物
  • 4. addEventListener と React の onXxx の使い分け
  • 5. ハマりどころ
  • 5-1. cancelable: false のイベントには効かない
  • 5-2. passive リスナーでは無視される
  • 5-3. React の onWheel / onTouchMove で preventDefault() が効かない
  • 5-4. <form> の中の <button> は既定で type="submit"
  • 参考