form内の特定のinput要素で、Enterキーを押したときにSubmitを防ぐ方法

## はじめに
実務で対応したバグの内容について、調べるとすぐに修正方法はわかったのですが、「簡単そうで、意外となんでそうなるのかよくわからない」内容でした。

調べた実装方法と検証結果を記録に残しておきます。

先に結論を書きます。

:::note info
`<input>`要素の`onKeyDown`イベントを使用して、Enterキーが押されたときにフォームのSubmitを防ごう！
```typescript
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!!`が出力されてしまいます😢これが出力されないようにしたいです。

https://codesandbox.io/embed/ltw9l4?view=preview&module=%2Fsrc%2FApp.tsx&expanddevtools=1

## 実装
調べてみると、`<form>`要素の中の`<input>`要素で、Enterキーが押されたときにフォームのSubmitを防ぐためには`onKeyDown`イベントを使うと簡単に実装できることがわかりました。

:::note info
`<input>`要素の`onKeyDown`イベントを使用して、Enterキーが押されたときにフォームのSubmitを防ごう！
```typescript
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>`要素にも到達します。

https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks/Events#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E3%83%90%E3%83%96%E3%83%AA%E3%83%B3%E3%82%B0

### preventDefaultでデフォルト動作をキャンセル

`handleKeyDown`と`handleSubmit`で`event.preventDefault()`が出てきました。
これにより、そのイベントのデフォルト動作がキャンセルされます。

それぞれ、キャンセルされているデフォルト動作が違うらしいです🤔

- `handleKeyDown`
`<input>`要素のEnterキーが押されると、通常は`<form>`要素のsubmitがトリガーされます。しかし、`preventDefault`を呼び出すと、このデフォルトのsubmit動作がキャンセルされます。

- `handleSubmit`
通常、フォームのsubmitボタンをクリックするか、フォーム内でEnterキーを押すと、ブラウザは以下の動作を行います。
  1. フォームデータの送信：フォーム内のすべての入力フィールドのデータをサーバーに送信します。送信先は、`<form>`要素のaction属性で指定されたURL
  2. ページのリロード：デフォルトでは、フォームが送信されると現在のページがリロードされる

handleSubmitの`preventDefault`については、消してみると確かにページがリロードされてしまったので、調べた内容は正しそうでした。

handleKeyDownの`preventDefault`については、ちょっとよくわからないので検証してみました。

#### 検証①: handleKeyDownのevent.preventDefault()を残す
```typescript:①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()を消す
```typescript:②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できているので要件も満たすことができました。

https://codesandbox.io/embed/pdssrl?view=preview&module=%2Fsrc%2Findex.tsx&expanddevtools=1

## 終わりに
「あるある」な実装のようで、修正方法について書かれている記事はたくさんありました。
自分で動かして検証したことで、納得できたのでよかったです！