はじめに
実務で対応したバグの内容について、調べるとすぐに修正方法はわかったのですが、「簡単そうで、意外となんでそうなるのかよくわからない」内容でした。
調べた実装方法と検証結果を記録に残しておきます。
先に結論を書きます。
<input>
要素のonKeyDown
イベントを使用して、Enterキーが押されたときにフォームのSubmitを防ごう!
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>):void => {
if (event.key === 'Enter') {
event.preventDefault();
}
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
};
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
/>
<button type="submit">Submit</button>
</form>
要件
-
<form>
要素の中に、複数の<input>
要素がある場合を考えます - このうち1つは、表示する要素を絞り込むための検索に使用する
<input>
要素です - このとき現在の実装では、検索で使用する
<input>
要素に文字を入力しEnterキーを押した時、<form>
要素のSubmitが実行されてしまいます - この
<input>
要素に文字を入力している状態でEnterキーを押したときは、<form>
要素のSubmitを実行させないようにしたいです - 他の
<input>
要素に文字を入力している状態でEnterキーを押したときは、<form>
要素のSubmitは実行して構いません
現在の実装
現在の実装を簡略化して示します。
<form>
要素のhandleSubmit
が実行されるとき、Form submitted!!
とconsoleに出力されるようにしました。
ユーザー検索用の<input>
要素に文字を入力しEnterキーを押すとForm submitted!!
が出力されてしまいます😢これが出力されないようにしたいです。
実装
調べてみると、<form>
要素の中の<input>
要素で、Enterキーが押されたときにフォームのSubmitを防ぐためにはonKeyDown
イベントを使うと簡単に実装できることがわかりました。
<input>
要素のonKeyDown
イベントを使用して、Enterキーが押されたときにフォームのSubmitを防ごう!
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>):void => {
if (event.key === 'Enter') {
event.preventDefault();
}
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
};
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
/>
<button type="submit">Submit</button>
</form>
解説
ここからは検証しながら、なぜこのような実装になるのかを考えていきます。
2つのポイントがあります。
イベントのバブリング
JavaScriptのイベントには「バブリング」という特性があります。イベントが発生した要素(今回は <input>
要素)から親要素(今回は<form>
要素)に伝播していきます。
この特性により、<input>
要素内で発生したonKeyDown
イベントは、<form>
要素にも到達します。
preventDefaultでデフォルト動作をキャンセル
handleKeyDown
とhandleSubmit
でevent.preventDefault()
が出てきました。
これにより、そのイベントのデフォルト動作がキャンセルされます。
それぞれ、キャンセルされているデフォルト動作が違うらしいです🤔
-
handleKeyDown
<input>
要素のEnterキーが押されると、通常は<form>
要素のsubmitがトリガーされます。しかし、preventDefault
を呼び出すと、このデフォルトのsubmit動作がキャンセルされます。 -
handleSubmit
通常、フォームのsubmitボタンをクリックするか、フォーム内でEnterキーを押すと、ブラウザは以下の動作を行います。- フォームデータの送信:フォーム内のすべての入力フィールドのデータをサーバーに送信します。送信先は、
<form>
要素のaction属性で指定されたURL - ページのリロード:デフォルトでは、フォームが送信されると現在のページがリロードされる
- フォームデータの送信:フォーム内のすべての入力フィールドのデータをサーバーに送信します。送信先は、
handleSubmitのpreventDefault
については、消してみると確かにページがリロードされてしまったので、調べた内容は正しそうでした。
handleKeyDownのpreventDefault
については、ちょっとよくわからないので検証してみました。
検証①: handleKeyDownのevent.preventDefault()を残す
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
console.log("onKeyDown event triggered");
if (event.key === "Enter") {
console.log("Enter key pressed");
event.preventDefault();
}
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log("Form submitted!!");
};
// 1. input要素にカーソルを当てて任意のキーを押す
// => onKeyDown event triggered
// 2. input要素にカーソルを当ててEnterキーを押す
// => onKeyDown event triggered
// => Enter key pressed
検証②: handleKeyDownのevent.preventDefault()を消す
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
console.log("onKeyDown event triggered");
if (event.key === "Enter") {
console.log("Enter key pressed");
}
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log("Form submitted!!");
};
// 1. input要素にカーソルを当てて任意のキーを押す
// => onKeyDown event triggered
// 2. input要素にカーソルを当ててEnterキーを押す
// => onKeyDown event triggered
// => Enter key pressed
// => Form submitted!!
出力結果から、確かにhandleKeyDownのpreventDefault
によって<form>
要素のsubmitがトリガーされるのが防がれていることが確認できました。
handleKeyDown
のpreventDefault
がない場合、イベントバブリングによって<input>
要素のhandleKeyDown
イベントが親要素の<form>
に伝播するので、handleSubmit
がトリガーされます。しかし、handleSubmit
のpreventDefault
がデフォルト動作を止めているので、フォームデータの送信等は実行されません。
名前を入力してEnterキーを押したときは、submitできているので要件も満たすことができました。
終わりに
「あるある」な実装のようで、修正方法について書かれている記事はたくさんありました。
自分で動かして検証したことで、納得できたのでよかったです!