Reactで非同期処理や状態変更を伴うUI操作を実装中に、イベントは発生しているのにボタンやスイッチの状態が変わらない問題が発生しました。
原因は、e.preventDefault() だったのですが、調査の内容をまとめたいと思います。
問題の背景
あるスイッチコンポーネントにおいて、状態変更のトリガーとして onChange イベントに非同期関数を割り当てていました。
const handleToggle = async (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault(); // ← これが問題の発端
await doSomethingAsync();
};
この状態でスイッチ(または checkbox)をクリックしても
1回目では切り替わらず、2回目のクリックでようやく切り替わる
という謎の挙動が発生しました。
原因: e.preventDefault() がデフォルト動作を止めていた
デフォルト動作とは?
HTML要素には、ユーザーの操作に対して ブラウザが自動的に行う処理(=デフォルト動作) があります。
| 要素 | 操作 | デフォルト動作 |
|---|---|---|
<a href="/about"> |
クリック | ページ遷移 |
<form> + <button> |
クリック | フォーム送信(リロード・POST等) |
<input type="checkbox"> |
クリック | チェック状態のON/OFFを切り替える |
これらの「自動処理」は、JavaScriptやTypescript内で e.preventDefault()を呼ぶことで無効化できます。しかし、これを e.preventDefault() で止めてしまうと以下のような事象が発生します。
- チェック状態が切り替わらない(見た目が変わらない)
- 状態管理は動いていてもUIが更新されないように見える
- 2回目のクリックでようやく変わる
解決策:e.preventDefault() を削除する
不要な e.preventDefault() を取り除くだけで、1回のクリックで正常に切り替わるようになります。
const handleToggle = async (e: React.ChangeEvent<HTMLInputElement>) => {
// e.preventDefault(); ← 削除!
const checked = e.target.checked;
await doSomethingAsync(checked);
};
e.preventDefault() を使うケース
| シチュエーション | 使用例 |
|---|---|
<form>の送信を止めてJSで処理したい |
onSubmit={(e) => { e.preventDefault(); handleSubmit(); }} |
<a>タグの遷移を防ぎたい |
onClick={(e) => { e.preventDefault(); openModal(); }} |
| スクロールや右クリックを抑止したい | e.preventDefault()でカスタム動作を強制する場合など |
避けるべきケース
| シチュエーション | 理由 |
|---|---|
| checkbox や switch の状態切り替えに使う | UI自体が切り替わらず、ユーザー混乱の元 |
| input フォーム内の通常入力や選択を止めたいとき | 入力不能になったり副作用を起こす可能性 |
まとめ
e.preventDefault()は「ブラウザが自動で行う動作」を止めたいときに使いましょう。
checkboxやswitchなど、状態の自動切り替えを止めてしまうと不具合の原因になります。