とりあえずReactでフォームを作るには

## はじめに
React Hook Formというフォームを作ることに特化したライブラリの基礎を紹介します。

今の業務で使っているのですが、見様見真似でやっている状態なので備忘も兼ねてまとめていきます。

なお、本記事においては
React 18.2.0
TypeScript 5.2.2
Vite 5.2.0
を使用しています。

## React Hook Formとは

### 概要
その名の通り、フォームを作ることに特化したライブラリです。

https://react-hook-form.com/

> Performant, flexible and extensible forms with easy-to-use validation.

とあるように、パフォーマンス、柔軟性、拡張性に優れており、また簡単にバリデーションを実行できるのが特徴です。

### どのようなものが作れるか
```react:App.tsx
import { useForm, SubmitHandler } from "react-hook-form";

type Inputs = {
  example: string;
  exampleRequired: string;
};
const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>();
  const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input defaultValue="test" {...register("example")} />
      <input {...register("exampleRequired", { required: true })} />
      {errors.exampleRequired && <span>This field is required</span>}
      <input type="submit" />
    </form>
  );
};

export default App;
```
公式サイトから抜粋してきたコードです。
（コメントなど一部不要な記載を削除）

画面は以下のようになります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/7a91600e-ea76-8c24-2aa5-6051f93975da.png'>

この状態で送信ボタンを押すと
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/4e9ce0b3-0ee1-92b4-6e75-32f2e3e4aed4.png'>

2つめの入力欄が必須である旨のメッセージが出ます。

値を入力するとメッセージは消えます。
この状態で送信ボタンを押すと
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/2453a01a-d899-6015-4308-e29a3c02f3f8.png'>

コンソールにフォームの入力値が出力されます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/a614c779-a038-4eb8-1113-c280f207e14b.png'>

それだけか、と思うかもしれませんが、実は結構すごいのです。

まずバリデーションの処理を書いていません。
`{ required: true }`みたいなそれらしきものは設定していますが、自身でフォームを作成する際にはしっかり書く必要があります。

また、フォームの入力値を管理するstateがありません。
送信後の値を取得する処理も書いていません。

この辺をまるっとやってくれているのがReactHookFormです。

では早速どのように書くのかを見ていきます。

## ReactHookFormを試す
### 導入
まずはインストールする必要があるので（プロジェクトは作成済の想定）、以下のコマンドを入力します。
```bash
npm install react-hook-form
```
以下のように完了すればオッケーです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/d8590a5d-37d1-7cb4-7995-a72287c0fc67.png)

### 入力フィールドの登録
この部分が一番大事な肝になります。
バリデーションとデータの送信に紐づけるための処理になります。

```react:App.tsx
import { useForm, SubmitHandler } from "react-hook-form";

interface IFormInput {
  firstName: string;
  check: string;
}
const App = () => {
  const {
    register,
    handleSubmit,
  } = useForm<IFormInput>();
  const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>First Name</label>
        <input {...register("firstName")}/>
      </div>

      <div>
        <label>Check</label>
        <input type="checkbox" {...register("check")}/>
      </div>
      <input type="submit" />
    </form>
  );
};

export default App;
```

#### useFormの使用
ReactHookFormの基本はuseFormというカスタムフックを使うことです。
このフックを使うことにより、様々な機能を使うことができます。

このuseFormにはオブジェクトの形で引数を渡すことでいろいろ設定ができるのですが、今回は割愛して、戻り値である`register`という関数に着目します。

#### register関数
inputタグ、またはselectタグに対して使用します。
引数としてname`属性に設定したい値を設定することで、バリデーションや値の送信時に紐づけることができます。

### バリデーションの適用
先ほど使用したregister関数に、どんなときにエラーとしたいのかという情報を追加できます。

```react:return文内のみ抜粋
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>First Name</label>
        <input
          {...register("firstName", {
            required: true,
            minLength: { value: 3, message: "3文字以上で入力して下さい" },
          })}
        />
      </div>
      <div>
        <label>Check</label>
        <input
          type="checkbox"
          {...register("check", {
            required: { value: true, message: "check is required" },
          })}
        />
      </div>
      <input type="submit" />
    </form>
  );
```

#### バリデーションの設定
register関数に、オブジェクト形式で引数を渡します。
requierdの他、min, max, minLength, maxLengthなどhtmlタグに設定することができる属性は一通り設定が可能です。

また、単純にバリデーションに使用したい設定値を渡す代わりに
`{value:設定値, message:'エラーメッセージ'}`
の形式でオブジェクトを渡すことで、エラーメッセージのカスタマイズも可能になっています。

### エラーハンドリング
上記でバリデーションの設定を行いましたが、今の状態でバリデーションエラーを起こしても何も起きません。

次にエラーハンドリングをする必要があります。

```react:App.tsx
import { useForm, SubmitHandler } from "react-hook-form";

interface IFormInput {
  firstName: string;
  check: string;
}
const App = () => {
  const {
    register,
    formState: { errors },
    handleSubmit,
  } = useForm<IFormInput>();
  const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>First Name</label>
        <input
          {...register("firstName", {
            required: true,
            minLength: { value: 3, message: "3文字以上で入力して下さい" },
          })}
        />
        {errors.firstName?.type === "required" && "必須です"}
        {errors.firstName?.type === "minLength" && errors.firstName.message}
      </div>

      <div>
        <label>Check</label>
        <input
          type="checkbox"
          {...register("check", {
            required: { value: true, message: "check is required" },
          })}
        />
        {errors.check && errors.check.message}
      </div>
      <input type="submit" />
    </form>
  );
};

export default App;
```
#### errosの使用
エラーの情報を扱うにはuseFormフックが返す`formState`の`errors`を使用します。

`errors`には`register`関数で登録した`name`に紐づく形でエラーの情報が格納されます。
エラーがない場合には何も入ってきません。

`errors`の`name`に紐づく`type`にはどのタイプのバリデーションエラーなのかが格納されます。
`firstName`には必須チェック(required)と、最低文字数チェック(minLength)を設定しているので、それぞれのエラーについて出力するメッセージを指定しています。

必須チェックの場合は`register`関数において`message`を指定しなかったので、任意のメッセージを指定。
最低文字数チェックの場合には設定した`message`を取得しています。

このように、`error`からバリデーションの結果を取得することによって、エラー時の挙動を決めることができます。


### 挙動確認
ではここまでで一通り設定ができたので動きを確認します。

初期状態です。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/baecc2e3-0283-3b42-037e-92a8edb25fae.png'>

そのまま送信ボタンを押すと、それぞれ必須チェックに引っかかります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/2263e989-1a8f-1283-9c25-b62589b37bd6.png'>

値を設定すると、Checkのエラーは消えますが、FirstNameは文字数のエラーに変わります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/050c57cc-8110-ae25-488c-65a7849b9fd6.png'>

適切な文字数を入力して送信すると
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/aaca9226-02d2-40fc-5539-ad556303429d.png'>

コンソールにフォームの値が出力されました。送信成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/30744a03-c67e-21ca-9ecf-67a992536b64.png)

## まとめ
ReactHookFormを使ってお手軽にフォームの作成、バリデーションを実施する方法を確認してきました。

自分であれこれ考えなくても、お作法に則ってパラメータを設定するだけであとはやってくれるので非常に楽です。

まだ上記で紹介した者は本当にさわりの部分だけなので、また機会を見つけていろいろな機能を紹介していきます。