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

# 概要
react-hook-formを使ってバリデーションを実装します。
実装が完了したら、ビルドを実行してビルドファイルを作成してみます。

# 完成イメージ
<img width="1596" alt="000001.jpg" src="https://dl.dropboxusercontent.com/s/na6amuxeduy8xoq/000001.jpg">
<img width="1596" alt="000010.jpg" src="https://dl.dropboxusercontent.com/s/nkdsflsqjfzzn1e/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 React from "react";
    import './styles/Contact.css';
+   import { useForm } from "react-hook-form";

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

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

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

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

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

```diff_javascript:contact.js

    <form onSubmit={handleSubmit(onSubmit)}>
        <div className="form_group">
            <label>お名前</label>
            <input type="text" id="name" {...register("name", { required: "お名前は必須です。" })} />
-           <span className="form_error">お名前は必須です。</span>
+           {errors.name && <span className="form_error">{errors.name.message}</span>}
        </div>
        <div className="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="form_error hidden" id="email_error">emailは必須かつemailの形式で入力してください。</span>
+           {errors.email && <span className="form_error">{errors.email.message}</span>}
        </div>
        <div className="form_group">
            <label>お問合せ内容</label>
            <textarea id="body" rows={4} {...register("body", {
                required: "お問合わせ内容は必須です。",
                maxLength: {
                    value: 10,
                    message: "10文字以下で入力してください。"
                }
            })}></textarea>
-            <span className="form_error hidden" id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
+           {errors.body && <span className="form_error">{errors.body.message}</span>}
        </div>
        <button className="submit_btn" id="submit_btn">送信</button>
    </form>

```
# 最終的なコード
最終的なコードはこのようになっています。

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

const 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="contact_form">
            <h2>お問合せ</h2>
            <form onSubmit={handleSubmit(onSubmit)}>
                <div className="form_group">
                    <label>お名前</label>
                    <input type="text" id="name" {...register("name", { required: "お名前は必須です。" })} />
                    {errors.name && <span className="form_error">{errors.name.message}</span>}
                </div>
                <div className="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="form_error">{errors.email.message}</span>}
                </div>
                <div className="form_group">
                    <label>お問合せ内容</label>
                    <textarea id="body" rows={4} {...register("body", {
                        required: "お問合わせ内容は必須です。",
                        maxLength: {
                            value: 10,
                            message: "10文字以下で入力してください。"
                        }
                    })}></textarea>
                    {errors.body && <span className="form_error">{errors.body.message}</span>}
                </div>
                <button className="submit_btn" id="submit_btn">送信</button>
            </form>
        </main >
    )
}
export default Contact;
```
保存して動作確認をします。
<img width="1381" alt="000010.jpg" src="https://dl.dropboxusercontent.com/s/nkdsflsqjfzzn1e/000010.jpg">

# ビルドする
Reactで作成したファイルを公開するためにはビルドをする必要があります。
> ビルドとはざっくり言うと人間にわかりやすく書いた言語をコンピューターが解釈しやすい言語に変換する作業という感じです。

:::note warn
外部に公開するときはbuildの中身をサーバにアップロードすればいいのですが、reactの仕組み上、DriveToWeb（GoogleDriveやMicrosft OnDriveにアップロードして公開できるサービス）では動作しないので、各環境にわせて設定する必要があります。（今後、この手順を公開するかもしれません）
:::


Reactのビルドコマンドは以下のとおりです。
react-websiteフォルダにいることを確認して下記を実行します。

```
npm run build
```
ビルドが完了すると、buildというフォルダが生成されています。

<img src="https://dl.dropboxusercontent.com/s/40qk686fzizm8vm/000020.jpg" width="300" alt="000020.jpg">

## 確認する
いままでは、開発サーバで確認していましたが、ビルドしたファイルが動くかどうかを確認していきたいと思います。

```
npm install -g serve
serve -s build
```
実行すると、サーバが起動します。
<img src="https://dl.dropboxusercontent.com/s/ykaotfgy1vt75o5/000030.jpg" width="300" alt="000030.jpg">
開発サーバと同じポートで起動するので確認しても、開発サーバとの差がわかりにくいですが、この状態で起動しているのはbuild後のファイルです。


# 関連コンテンツ
- 前：[Reactで簡易コーポレートサイトを作成する #2 -ナビゲーションバーとフッター編-](https://qiita.com/minimumskills/private/196cd269af311b06358c)