React Hook Form：基本的な使い方

# React Hook Form：基本的な使い方

## React Hook Formを使う理由

React Hook Formを使わない場合は以下のようなデメリットがあります。

- **フィールドのステート管理の手動化:**

各フィールドの値を個別に useState や useReducer で管理する必要があり、ステートの数が増える。各フィールドに対して、入力内容の変更を手動で追跡する必要がある。

- **バリデーションの手動実装:**

バリデーションのロジックを手動で実装し、エラーの状態も自分で管理する必要がある。各フィールドのエラーを保持し、表示するために別途エラーハンドリングが必要。

- **フォームのサブミット状態の管理:**

フォームがサブミット中かどうかの状態を手動で管理し、UIでその状態に応じた表示（例: "Loading..."）を更新する必要がある。

- **パフォーマンスの問題:**

各入力フィールドが状態を持っている場合、フィールドの数が多いと状態変更が頻繁に発生し、パフォーマンスの低下が見られる可能性がある。

- **フォームのリセット・初期化の手間:**

フォームをリセットしたり、初期値を設定したりする際、手動ですべてのフィールドの状態を更新する必要がある。

- **一貫性の欠如:**

各フォームに対して手動で実装するロジック（バリデーション、エラーハンドリングなど）が異なる場合があり、コードの再利用性が低くなる。


## React Hook Formを使った例

以下がReact Hook Formを使った例です。
より実用的な使い方はZodなどのバリデーションライブラリを使うことですが、勉強のため今回はシンプルに実装していきたいと思います。

```typescript
import { SubmitHandler, useForm } from "react-hook-form";

type FormFields = {
  email: string
  password: string
};

const Form = () => {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    setError,
  } = useForm<FormFields>();

  const onSubmit: SubmitHandler<FormFields> = async (data) => {

    try {
      const response = await fetch("https://example.com/api/login", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error("Failed to log in");
      }

      console.log("Login successful");
    } catch (error) {
      setError("root", { message: error.message || "Failed to log in" });
    }
  };
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="text"
        {...register("email", {
          required: "Email is required",
          validate: (value) => {
            if (!value.includes("@")) {
              return "Email must include @";
            }
            return undefined;
          },
        })}
      />
      {errors.email && (
        <div>{errors.email.message}</div>
      )}
      <input
        type="text"
        {...register("password", {
          required: "Password is required",
          minLength: {
            value: 8,
            message: "Password must have at least 8 characters",
          },
        })}
      />
      {errors.password && (
        <div>{errors.password.message}</div>
      )}
      <button
        type="submit"
        disabled={isSubmitting}
      >
        {isSubmitting ? "Loading..." : "Submit"}
      </button>
      {errors.root && <div>{errors.root.message}</div>}
    </form>
  );
};

export default Form;
```

### 解説

React Hook FormのuseFormから何を取得すればいいか困りますよね。
家を建てたいのに道具箱から何を使えばいいか分からない感覚と一緒です。
フォーム送信時に何が行われるかが分かれば簡単です。

#### フォームの送信
1. 送信ボタンをクリック
2. 入力欄の値が適切かバリデーション
3. 入力欄の値が適切であればフォームを送信
4. フォームを送信中の状態を表示
5. フォーム送信が失敗した場合のエラーハンドリング  

↓この流れを頭に入れると、以下の道具が必要になることがわかります

- `register`で以下を実行
  - 各入力欄をreact hook formに登録する
  - 各入力欄のバリデーションを設定
- `handleSubmit`で以下を実行
  - 各入力欄のバリデーションを実行し、成功時のみフォームを送信
  - フォーム送信時に発生したエラーを自動的に処理
  - フォームの送信の際に発生する非同期処理やステート更新を管理） 
- `formState: {errors}`でバリデーションが失敗したときに各エラーを表示
- `formState: {isSubmitting}`でフォーム送信中の状態を表示やボタンを無効化
- `setError`でフォーム送信時のエラーを設定

バリデーションはZodなどのバリデーションライブラリを併せて使うとさらに実用的にReact Hook Formを使えるので、次の記事で紹介します。