Next.jsで簡易コーポレートサイトを作成する #3 -バリデーションとビルド編-

# 概要
react-hook-formを使ってバリデーションを実装します。
実装方法はReactと同様です。

# 完成イメージ
<img width="1596" alt="000001.jpg" src="https://dl.dropbox.com/s/i04kqi1jn16vzxg/000001.jpg">

<img width="1596" alt="000010.jpg" src="https://dl.dropbox.com/s/x6enec5fxe81pgb/000010.jpg">

# バリデーション実装
## react-hook-formのインストール
react-hook-formというライブラリを使って、簡単にバリデーションを追加することができます。

```
npm install react-hook-form
```


## 宣言
Contact.jsを編集してreact-hook-formを適用していきます。
useForm()から必要なオブジェクトを宣言します。

| キーワード | 内容 |
|:--|:--|
| useForm | フォームを簡単に管理するためのカスタムフック |
| register | フォームに適用したいルールをフックに登録するメソッド |
| handleSubmit | バリデーションを通過したときにフォームのデータを受け取るメソッド |
| formState | フォームの状態や情報をすべて持っている。そのためここではバリデーションエラーのメッセージを取得している |

```diff_javascript:contact.js
    import styles from '../styles/Contact.module.css';
+   import { useForm } from "react-hook-form";

    export default function Contact() {
+       const {
+           register,
+           handleSubmit,
+           formState: { errors }
+       } = useForm();
        
```

```javascript:コピペ用
const {
    register,
    handleSubmit,
    formState: { errors }
} = useForm();
```

## onSubmit関数設定
送信ボタンがクリックされたときに実行されるonSubmit関数を設定します。
これは、APIにデータを送る想定で記述しています。

```diff_javascript:contact.js
    import styles from '../styles/Contact.module.css'
    import { useForm } from "react-hook-form"

    export default function Contact() {
        const {
            register,
            handleSubmit,
            formState:{errors}
        } = useForm();

+       const onSubmit = (data) => {
+           const api_url = 'apiのURL';
+
+           fetch(api_url, {
+               method: "post",
+               headers: {
+                   "Content-Type": "application/x-www-form-urlencoded",
+               },
+               body: encodeURI(`name=${data.name}&email=${data.email}&body=${data.body}`)
+           })
+               .then((response) => response.json())
+               .then((result) => alert(result.message))
+               .catch((error) => alert(error.message))
+       }
```

```javascript:コピペ用
const onSubmit = (data) => {

    const api_url = 'apiのURL';

    fetch(api_url, {
        method: "post",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
        body: encodeURI(`name=${data.name}&email=${data.email}&body=${data.body}`)
    })
        .then((response) => response.json())
        .then((result) => alert(result.message))
        .catch((error) => alert(error.message))
}
```

## formタグでonSubmit()関数呼び出し
フォームが送信されると、作成した関数onSubmit()関数を呼び出すようにformタグにonSubmitを設定します。

```diff_javascript:contact.js
-      <form>
+      <form onSubmit={handleSubmit(onSubmit)}>
```

## props追加
nameとemail、bodyフィールドに{...register(...)}というpropを追加します。
registerの中では、required属性とpattern属性、maxLength属性を指定します。
この2つにより、フィールドが正しくない場合にエラーを表示することができます。
それぞれの記述するとこのようになります。

```javascript:contact.js（formのみ抜粋）
<form onSubmit={handleSubmit(onSubmit)}>
    <div className={styles.form_group}>
        <label>お名前</label>
        <input type="text" id="name" {...register("name", { required: "お名前は必須です。" })} />
        <span className={styles.form_error} id="name_error">お名前は必須です。</span>
    </div>
    <div className={styles.form_group}>
        <label>email</label>
        <input type="email" id="email" {...register("email", {
            required: "emailは必須です。",
            pattern: {
                value: /^[a-z0-9.]+@[a-z]+\.[a-z]+$/,
                message: "emailの形式で入力してください。"
            }
        })} />
        <span className={styles.form_error} id="email_error">emailは必須かつemailの形式で入力してください。</span>
    </div>
    <div className={styles.form_group}>
        <label>お問合せ内容</label>
        <textarea id="body" rows={4} {...register("body", {
            required: "お問合わせ内容は必須です。",
            maxLength: {
                value: 10,
                message: "10文字以下で入力してください。"
            }
        })}></textarea>
        <span className={styles.form_error} id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
    </div>
    <button className={styles.submit_btn} id="submit_btn">送信</button>
</form>
```
次に、エラーメッセージを動的に表示するようにコードを編集します。
各エラーメッセージを下記の様に`{errors.name && 左の条件にマッチした場合の動作}`を使ってエラーメッセージがあれば表示するようにします。

```diff_javascript:contact.js
    <form onSubmit={handleSubmit(onSubmit)}>
        <div className={styles.form_group}>
            <label>お名前</label>
            <input type="text" id="name" {...register("name", { required: "お名前は必須です。" })} />
-           <span className={styles.form_error}>{errors.name.message}</span>
+           {errors.name && <span className={styles.form_error}>{errors.name.message}</span>}
        </div>
        <div className={styles.form_group}>
            <label>email</label>
            <input type="email" id="email" {...register("email", {
                required: "emailは必須です。",
                pattern: {
                    value: /^[a-z0-9.]+@[a-z]+\.[a-z]+$/,
                    message: "emailの形式で入力してください。"
                }
            })} />
-           <span className={styles.form_error} id="email_error">emailは必須かつemailの形式で入力してください。</span>
+           {errors.email && <span className={styles.form_error}>{errors.email.message}</span>}
        </div>
        <div className={styles.form_group}>
            <label>お問合せ内容</label>
            <textarea id="body" rows={4} {...register("body", {
                required: "お問合わせ内容は必須です。",
                maxLength: {
                    value: 10,
                    message: "10文字以下で入力してください。"
                }
            })}></textarea>
-           <span className={styles.form_error} id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
+            {errors.body && <span className={styles.form_error}>{errors.body.message}</span>}
        </div>
        <button className={styles.submit_btn} id="submit_btn">送信</button>
    </form>
```
# 最終的なコード
最終的なコードはこのようになっています。

```javascript:contact.js
import styles from '../styles/Contact.module.css';
import { useForm } from "react-hook-form";

export default function Contact() {
    const {
        register,
        handleSubmit,
        formState: { errors }
    } = useForm();

    const onSubmit = (data) => {

        const api_url = 'apiのURL';

        fetch(api_url, {
            method: "post",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            },
            body: encodeURI(`name=${data.name}&email=${data.email}&body=${data.body}`)
        })
            .then((response) => response.json())
            .then((result) => alert(result.message))
            .catch((error) => alert(error.message))
    }
    return (
        <main className={styles.contact_form}>
            <h2>お問合せ</h2>
            <form onSubmit={handleSubmit(onSubmit)}>
                <div className={styles.form_group}>
                    <label>お名前</label>
                    <input type="text" id="name" {...register("name", { required: "お名前は必須です。" })} />
                    {errors.name && <span className={styles.form_error}>{errors.name.message}</span>}
                </div>
                <div className={styles.form_group}>
                    <label>email</label>
                    <input type="email" id="email" {...register("email", {
                        required: "emailは必須です。",
                        pattern: {
                            value: /^[a-z0-9.]+@[a-z]+\.[a-z]+$/,
                            message: "emailの形式で入力してください。"
                        }
                    })} />
                    {errors.email && <span className={styles.form_error}>{errors.email.message}</span>}
                </div>
                <div className={styles.form_group}>
                    <label>お問合せ内容</label>
                    <textarea id="body" rows={4} {...register("body", {
                        required: "お問合わせ内容は必須です。",
                        maxLength: {
                            value: 10,
                            message: "10文字以下で入力してください。"
                        }
                    })}></textarea>
                    {errors.body && <span className={styles.form_error}>{errors.body.message}</span>}
                </div>
                <button className={styles.submit_btn} id="submit_btn">送信</button>
            </form>
        </main >
    )
}
```
保存して動作確認をします。
<img width="1381" alt="000020.jpg" src="https://dl.dropbox.com/s/bu0b44siht4p0z7/000020.jpg">

# ビルド
## package.json編集
ページを公開できるようにするため、ビルドを実行します。
まず、package.jsonを編集してビルドスクリプトを更新します。
ビルドを実行し、生成されたファイルをoutフォルダに出力するようにします。

```diff_json:package.json
    "scripts": {
        "dev": "next dev",
-       "build": "next build",
+       "build": "next build && next export",
        "start": "next start",
        "lint": "next lint"
    },
```



## ビルドする
ターミナルでnext-websiteフォルダ内にいることを確認して下記を実行します。

```
npm run build
```
するとファイルが生成されるのがターミナルでわかります。
今回は簡単なコーポレートサイトなので静的ページのみが生成され（Statice）に○がついているのがわかります。
![000030.jpg](https://dl.dropbox.com/s/37dy2axb6r34hk9/000030.jpg)

フォルダを確認すると.nextフォルダとoutフォルダが増えています。
.nextフォルダにビルド結果が書き出されます。
outフォルダには生成されたHTMLファイルやJSファイルが格納されます。
<img src="https://dl.dropbox.com/s/26i19361x9u5d4d/000040.jpg" width="300" alt="000040.jp">

## 確認
今までは`npm run dev`で一時的にビルドををしてサーバを起動して確認していましたが、ビルドした結果をサーバを起動して確認してみます。

```
npm run start
```
ブラウザで表示される内容に変化がない（ビルドによっておかしな挙動になってない）ことを確認して完成です。

# 関連コンテンツ
* 前：[Next.jsで簡易コーポレートサイトを作成する #2 -ナビゲーションバーとフッター編-](https://qiita.com/minimumskills/private/d5c0e2d0ced5766b295b)
* [超シンプルなWeb（アプリ）作成を通して各種フロントエンド技術を体験する - 2023 Early -](https://qiita.com/minimumskills/private/9e7efe86e244bb9844b0)