【Next.js React】React-Hook-Formでバリデーションを実装

# サンプル
<b>ログイン画面で①ユーザー名、②E-mail、③パスワードを空にしてみます。そのあと、登録ボタンをクリックすると...</b>
![デフォルト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/728c50ef-9664-fc59-cab4-5e079286f7e7.png)

<b>下記の画面のようにそれぞれエラーが出るようなサンプルを作ります。</b>
![NG画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/cb622e68-3483-b088-169e-5e47aab9bcbb.png)

# パッケージをインストールする

`react-hook-form`を使う場合は、パッケージ管理ツールを使って`module`をインストールしましょう。
```
npm install react-hook-form
```
これで準備は整いました。

# サンプルコード

まずは全コードを記載します。
```app/admin/login/pag.tsx
'use client' // 付けること
import { useState } from "react";
import { useForm } from "react-hook-form";
//import RegisterUser from "@/app/api/userRegister";

export default function Login() {
    // 既定値を準備する
    const defaultValues = {
        userName: '山田太郎',
        email: 'sample@email.com',
        password: ''
    }

    // フォームを初期化する
    const { register, handleSubmit, formState: { errors } } = useForm({
        defaultValues
    });

    const [userName, setUserName] = useState("");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    // フォーム送信時に実行される関数
    const onSubmit = async (data: any) => {
        //e.preventDefault();
        console.log("フォーム送信データ：", data);

        // Postリクエスト送信
        const response = await fetch('/api/userRegister', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            
            body: JSON.stringify({
              userName: data.userName,
              email: data.email,
              password: data.password,
            }),
        });

        // レスポンスを確認する
        if (response.ok) {
            const result = await response.json();
            console.log('ユーザー登録成功：', result);
        } else {
            console.log('ユーザー登録失敗');
        }
    }

    return (
        <div className="flex items-center justify-center min-h-screen rounded-md">
            <form onSubmit={handleSubmit(onSubmit)} className="shadow-md w-96 px-8 py-8 border border-gray-300 rounded-md">
                <div className="w-full">
                    <label className="font-bold">ユーザー名</label>
                    <input
                        type="text"
                        name="userName"
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        defaultValue={userName}
                        placeholder=""
                        {...register('userName', {
                            required: '名前は必須です',
                            maxLength: {
                                value: 20,
                                message: '名前は20文字以内にしてください。'
                            }
                        })}
                        onChange={(e) => setUserName(e.target.value)}
                    />
                    <div className="text-red-500">{errors.userName?.message}</div>
                </div>
                <div className="w-full mt-4">
                    <label className="font-bold">E-mail</label>
                    <input
                        type="email"
                        {...register('email', {
                            required: 'メールアドレスは必須です。',
                            pattern: {
                                value: /([a-z\d+\-.]@[a-z\d-]+(?:\.[a-z]+)*)/i,
                                message: 'メールアドレスの形式が不正です。'
                            }
                        })}
                        name="email"
                        defaultValue={email}
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        onChange={(e) => setEmail(e.target.value)}
                    />
                    <div className="text-red-500">{errors.email?.message}</div>
                </div>
                <div className="w-full mt-4">
                    <label className="font-bold">パスワード</label>
                    <input
                        type="password"
                        {...register('password', {
                            required: 'パスワードは必須です。'
                        })}
                        name="password"
                        defaultValue={password}
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        onChange={(e) => setPassword(e.target.value)}
                    />
                    <div className="text-red-500">{errors.password?.message}</div>
                </div>
                <div className="flex items-center justify-center mt-4 mb-4">
                    <button type="submit" className="shadow-md bg-blue-500 text-white font-bold rounded-md px-4 py-4">
                        登録
                    </button>
                </div>
            </form>
        </div>
    );
}

```

# 解説
## defaultValueで規定値を設定
プレースホルダーみたいに初期の画面に何らかの値を持たせるために、`defaultValue`を使って設定しています。

```app/admin/login/pag.tsx
// 既定値を準備する
const defaultValues = {
    userName: '山田太郎',
    email: 'sample@email.com',
    password: ''
}
// フォームを初期化する
const { register, handleSubmit, formState: { errors } } = useForm({
    defaultValues
});
```

## register{...}オブジェクトのバリデーションを持たせる
register{...}オブジェクトは、フォーム要素に紐づけるイベントハンドラーなどを含んだオブジェクトです。

なので、下記のように表示させたいエラーハンドリングを付けると良いです。
 ### input type="text"の場合
```app/admin/login/pag.tsx
                <div className="w-full">
                    <label className="font-bold">ユーザー名</label>
                    <input
                        type="text"
                        name="userName"
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        defaultValue={userName}
                        placeholder=""
                        {...register('userName', {
                            required: '名前は必須です',
                            maxLength: {
                                value: 20,
                                message: '名前は20文字以内にしてください。'
                            }
                        })}
                        onChange={(e) => setUserName(e.target.value)}
                    />
                    <div className="text-red-500">{errors.userName?.message}</div>
                </div>
```
 ### input type="email"の場合
```app/admin/login/pag.tsx
                <div className="w-full mt-4">
                    <label className="font-bold">E-mail</label>
                    <input
                        type="email"
                        {...register('email', {
                            required: 'メールアドレスは必須です。',
                            pattern: {
                                value: /([a-z\d+\-.]@[a-z\d-]+(?:\.[a-z]+)*)/i,
                                message: 'メールアドレスの形式が不正です。'
                            }
                        })}
                        name="email"
                        defaultValue={email}
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        onChange={(e) => setEmail(e.target.value)}
                    />
                    <div className="text-red-500">{errors.email?.message}</div>

```
 ### input type="password"の場合

```app/admin/login/pag.tsx
                <div className="w-full mt-4">
                    <label className="font-bold">パスワード</label>
                    <input
                        type="password"
                        {...register('password', {
                            required: 'パスワードは必須です。'
                        })}
                        name="password"
                        defaultValue={password}
                        className="block rounded-md w-full text-sm border border-gray-300 text-gray-900 px-2 py-2"
                        onChange={(e) => setPassword(e.target.value)}
                    />
                    <div className="text-red-500">{errors.password?.message}</div>
                </div>
```

以上です、