ボタンには常にtype="button"をつけよう
結論
ボタンを実装する際には、フォームのサブミットボタンでなければtype="button"をつけた方が良いです。
なぜか
HTMLのボタン要素は3つのtype属性を持つことができます。
-
submit
type="submit"のボタンはフォームのデータをサーバーへ送信します。 -
button
type="button"のボタンは規定の動作がなく、イベントハンドラーを渡して使うのが一般的となります。 -
reset
type="reset"のボタンはすべてのコントロールを初期値の初期化します。
<input type="reset">と同様です。
そしてデフォルトのtype属性の値はsubmitです。
つまりtype属性を指定せずにボタンを置くとtype="submit"として動作します。
起こった問題
予想外の挙動をするパターン1
以下は郵便番号と住所を入力するシンプルなフォームです。
郵便番号に基づいて住所を検索できるボタンも設置しました。よくみるUIのパターンですね。
このとき、郵便番号を入力したあとにsearchボタンを押した場合、どのような挙動になるでしょうか?
const Form = () => {
return (
<main>
<form
onSubmit={() => alert('submit!')}
>
<input
type="text"
placeholder="postcode"
/>
<button
onClick={() => alert('search!')}
>
search
</button>
<input
type="text"
placeholder="address"
/>
<button type="submit">
submit
</button>
</form>
</main>
);
};
export default Form;
上のソースコードの場合、以下のように動作します。

なんとsearch!と出たあとにsubmit!も表示されています。
つまりサブミットボタンが押され、フォームが送信されているということです。
予想外の挙動をするパターン2
先ほどと同じフォームですべての項目を入力し、今度はEnterキーを押してみます。
一般的なフォームの場合、Enterキーを押すことでフォームが送信されることを想定します。
しかし上記のソースコードの場合、以下のように動作します。

こちらも先ほどと同様にsearch!が出力され、そのあとにsubmit!が表示されます。
安全なフォーム
はじめに出てきたソースコードに少し変更を加えます。
結論で紹介したようにサブミットボタン以外のボタンにtype="button"をつけます。
以下のようになります。見やすいようにスタイルを除いています。
const Form = () => {
return (
<main>
<form
onSubmit={() => alert('submit!')}
>
<input
type="text"
placeholder="postcode"
/>
<button
+ type="button"
onClick={() => alert('search!')}
>
search
</button>
<input
type="text"
placeholder="address"
/>
<button type="submit">
submit
</button>
</form>
</main>
);
};
export default Form;
上記のソースコードの場合、以下のように動作します。
searchボタンを押したとき

Enterキーを押したとき

どちらも期待した通りに動作します。
まとめ
フォームを作成する際には、サブミットボタン以外はボタンにtype="button"を設定する習慣をつけることは非常に大切だと思います。思いもよらぬ挙動やバグを未然に防ぐことができます。
またJavaScriptで特別な動作を実装する場合は必ずtype="button"を使うのが良いと思います。
UIフレームワークをラップして使う場合はデフォルトを設定しなおして使うのも良いですね。
参考
Discussion