【React】react-hook-formで簡単にフォーム作成

## この記事の目的
- react-hook-formを使用したときの備忘録
- Reactのプロジェクトでログイン機能追加する際の備忘録


## 概要
- React-hook-formについて
- 下準備
- React-hook-formインストール
- React-hook-formの使い方
- 【補足】バリデーション管理（zod）

## React Hook Formについて
>React Hook FormはReact用のフォームバリデーションライブラリです。input要素に入力した値を取得するだけではなくバリデーション機能なども備えており簡単にフォームを実装することができます。入力フォームの作成が嫌いな人もライブラリの力を借りることでフォーム作成の手間を軽減することができます。バリデーションについてはReact Hook Form自身も機能を備えていますがバリデーションライブラリのYup、Zodを利用した場合の設定方法についても説明しています。

簡単にいうとバリデーションもついた入力フォームを簡単に作成できるやつってことですかね！
[React-hook-form の公式サイト](https://react-hook-form.com/)

#### これまでのやり方
これまでログインフォームのようなものを作成する際は、ログイン時のユーザ名やメールアドレス、パスワードをuseStateを使用して行わないといけなかった。
これを行うとformにサブミット関数作成や、各要素の状態管理を行いコードも増えるしただフォームを作るだけなのにめんどくさいって感じでした

## 下準備
早速React-hook-formを使用していきたいのですが、基本的なフォームの画面部分を作成しておきます。
ちなみにReactのTypeScriptを使用しています
今回は下記のような感じで土台となるフォームを作成し使用していきたいと思います
![スクリーンショット 2023-04-09 17.10.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462658/0eef8e35-6cf2-dd5d-cd2a-5b81edd22217.png)
<details><summary>画像のフォームのソースコード（src/App.tsx）についてはこちらから</summary><div>

```src/App.tsx
import { AiFillLock, AiFillMail } from "react-icons/ai";
import { FaUser } from "react-icons/fa";
import "./App.css";


function App() {
  return (
    <>
      <section>
        <h1>Login From</h1>
        <form>
          <label htmlFor="username">
            <FaUser color="#888888" />
            ユーザー名
          </label>
          <input
            id="username"
            type="text"
            placeholder="username"
          />

          <label htmlFor="email">
            <AiFillMail color="#888888" />
            メールアドレス
          </label>
          <input
            id="email"
            type="email"
            placeholder="email"
          />

          <label htmlFor="password">
            <AiFillLock color="#888888" />
            パスワード
          </label>
          <input
            id="password"
            type="password"
            placeholder="password"
          />

          <button type="submit">登録</button>
        </form>
      </section>
    </>
  );
}

export default App;


```
</div></details>

<details><summary>画像のフォームのソースコード（src/App.css）についてはこちらから</summary><div>

```src/App.css
/* 全体のスタイリング */
body {
  font-family: "Arial", sans-serif;
  background-color: #ebebeb !important;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
}

/* セクションのスタイリング */
section {
  background-color: #fff;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 400px;
}

/* フォームのコンテナ */
.form-container {
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 400px;
}

/* タイトル */
h1 {
  margin: 0 0 20px;
  font-size: 24px;
  font-weight: bold;
  text-align: center;
}

/* フォーム要素のスタイリング */
label {
  display: block;
  font-size: 14px;
  margin-bottom: 5px;
}

input {
  width: 100%;
  padding: 8px;
  font-size: 14px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  margin-bottom: 5px;
  box-sizing: border-box;
}

/* エラーメッセージ */
p {
  color: #e3342f;
  margin: 0 0 15px;
  font-size: 12px;
}

/* 送信ボタン */
button {
  width: 100%;
  padding: 8px;
  font-size: 16px;
  font-weight: bold;
  background-color: #48c774;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s;
  margin-top: 20px !important;
}

button:hover {
  background-color: #35a363;
}


```
</div></details>

React-iconを使用しているのでインストールコマンドを下記に記載しておきます
また、[React-iconについてはこちら](https://qiita.com/pikimaru/items/bffc051a248f8e2ab2b2)を参考にしてみて下さい
```
npm install react-icons --save
```

## React-hook-formインストール
下記のコマンドでインストール
```
npm install react-hook-form
```
こちらのコマンドは[React-hook-form の公式サイト](https://react-hook-form.com/)に記載されています

## React-hook-formの使い方
下記のように編集していきます。
React-hook-form記載部分はコメントアウトで説明しています。
今回は簡単なバリデーションについてもつけています
```src/App.tsx
import { AiFillLock, AiFillMail } from "react-icons/ai";
import { FaUser } from "react-icons/fa";
import "./App.css";

// React-hook-formライブラリのuseForm関数をインポート
import { useForm } from "react-hook-form";

// ログインフォームのデータ型を定義
interface LoginForm {
  username: string;
  email: string;
  password: string;
}

function App() {
  // useForm関数を呼び出して、各種設定を行う
  const {
    register, // inputタグとバリデーションルールを紐付けるための関数
    handleSubmit, // フォームのsubmitイベント時に呼ばれる関数
    formState: { errors }, // バリデーションエラーの情報が格納
  } = useForm<LoginForm>({ mode: "onChange" }); // mode: "onChange"で入力時バリデーション

  // フォームのsubmitイベントで呼ばれる関数
  const onSubmit = (data: LoginForm) => console.log(data);

  return (
    <>
      <section>
        <h1>Login From</h1>
        <form onSubmit={handleSubmit(onSubmit)}>
          <label htmlFor="username">
            <FaUser color="#888888" />
            ユーザー名
          </label>
          {/* {...register("フィールド名", { ルール })}で、フィールド登録、必ルールを設定 */}
          <input
            id="username"
            type="text"
            placeholder="username"
            {...register("username", {
              required: "ユーザー名を入力して下さい",
              minLength: { value: 5, message: "５文字以上で入力して下さい" },
            })}
          />
          <p>{errors.username?.message as React.ReactNode}</p>

          <label htmlFor="email">
            <AiFillMail color="#888888" />
            メールアドレス
          </label>
          {/* {...register("フィールド名", { ルール })}で、フィールド登録、必ルールを設定 */}
          <input
            id="email"
            type="email"
            placeholder="email"
            {...register("email", {
              required: "メールアドレスを入力して下さい",
            })}
          />
          <p>{errors.email?.message as React.ReactNode}</p>

          <label htmlFor="password">
            <AiFillLock color="#888888" />
            パスワード
          </label>
          {/* {...register("フィールド名", { ルール })}で、フィールド登録、必ルールを設定 */}
          <input
            id="password"
            type="password"
            placeholder="password"
            {...register("password", {
              required: "パスワードを入力して下さい",
              minLength: { value: 6, message: "6文字以上で入力して下さい" },
            })}
          />
          <p>{errors.password?.message as React.ReactNode}</p>

          <button type="submit">登録</button>
        </form>
      </section>
    </>
  );
}

export default App;

```
### 関数説明
|名前|説明|
|---|---|
| register  |　inputなどに入力された値を参照するために使う |
| handleSubmit  |　ラップした関数にformのdataをオブジェクトの形で渡してくれる。on_submitの引数にdataがあるのはそのため |
| formState  |　form内の入力の有無や送信の状態などを取得できる |
### バリデーション説明
|名前|説明|
|---|---|
|required|必須項目にするか|
|maxLength|最大文字数|
|minLenght|最小文字数|
|max|最大データ量|
|min|最小データ量|
|pattern|文字の形式|
|onChange|onChangeイベント時に実行される関数|

![スクリーンショット 2023-04-09 17.38.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462658/3c5dfcef-6883-c872-3582-37b976bec3f1.png)




## 【補足】バリデーション管理（zod）
Zodはバリデーションにスキーマを設定できるもの。Validationライブラリ。
簡単にいうと、上記で挙げているコードのようにinput内にバリデーション直書きしないでまとめて定義して管理しやすくしていく感じかと思います。
[zodの公式サイト](https://zod.dev/?id=table-of-contents)
### Zodインストール
`useForm`の`resolver`で利用することで`zod`が使えるようになるため`@hookform/resolvers`もインストールします
```
npm install zod
npm install @hookform/resolvers
```
### バリデーション定義
今回は`src/utils/validationSchema.ts`というファイルを作成しています
```src/utils/validationSchema.ts
import { z } from "zod";

// Zodによるバリデーションスキーマの定義
export const validationSchema = z.object({
  username: z
    .string()
    .nonempty("ユーザー名を入力して下さい") // 非空文字列チェック
    .min(5, "５文字以上で入力して下さい"), // 最小文字数チェック
  email: z
    .string()
    .nonempty("メールアドレスを入力して下さい") // 非空文字列チェック
    .email("メールアドレスを正しい表記で入力して下さい"), // Email形式チェック
  password: z
    .string()
    .nonempty("パスワードを入力して下さい") // 非空文字列チェック
    .min(6, "6文字以上で入力して下さい"), // 最小文字数チェック
});
```

### フォーム部分の変更
`src/App.tsx`を下記のように変更すればバリデーションが反映されるかと思います
```src/App.tsx
import { AiFillLock, AiFillMail } from "react-icons/ai";
import { FaUser } from "react-icons/fa";
import "./App.css";
import { useForm } from "react-hook-form";
import { validationSchema } from "./utils/validationSchema";
import { zodResolvers } from "@hookform/resolvers/zod";

interface LoginForm {
  username: string;
  email: string;
  password: string;
}

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginForm>({
    mode: "onChange",
    resolver: zodResolvers(validationSchema),
  });
  return (
    <>
      <section>
        <h1>Login From</h1>
        <form onSubmit={handleSubmit(onSubmit)}>
          <label htmlFor="username">
            <FaUser color="#888888" />
            ユーザー名
          </label>
          {/* {...register("フィールド名", { ルール })}で、フィールド登録、必ルールを設定 */}
          <input
            id="username"
            type="text"
            placeholder="username"
            {...register("username")}
          />
          <p>{errors.username?.message as React.ReactNode}</p>

          <label htmlFor="email">
            <AiFillMail color="#888888" />
            メールアドレス
          </label>
          <input
            id="email"
            type="email"
            placeholder="email"
            {...register("email")}
          />
          <p>{errors.email?.message as React.ReactNode}</p>

          <label htmlFor="password">
            <AiFillLock color="#888888" />
            パスワード
          </label>
          <input
            id="password"
            type="password"
            placeholder="password"
            {...register("password")}
          />
          <p>{errors.password?.message as React.ReactNode}</p>

          <button type="submit">登録</button>
        </form>
      </section>
    </>
  );
}

export default App;

```