React + TypeScript + Ant Designでお問合せフォームを作る

## はじめに

本記事は、
「フロントエンドの開発効率を向上するヒントを教え合おう！」イベントの参加記事です。

https://qiita.com/official-events/5fdc281997d5754d8ac9

前回、Reactの環境構築からAnt Designを適用させるところまで行いました。
今回はそこからお問合せフォームを簡単に作成していきたいと思います。

https://qiita.com/shimajiri/items/2cfd380db0382d1f9a58


## 要件を決める

とりあえずお問合せフォームの例として、
「名前」、「性別」、「年齢」、「メールアドレス」、「都道府県」、「お問合せ内容」
これらを作成したいと思います。

以下のようなお問合せフォームを目指します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/36cef27f-6189-50f2-85fe-b658edcd5357.png)


## お問合せフォームを実装する

前回と同じようにApp.tsxの中身を書き換えていきます。

それでは、細かい実装について順番に解説をしていきます。
※一番最後に完成版のソースコードを載せておきますので良くわからなくなったら、
そちらを見ていただいても良いかと思います。

まずはFormタグでお問合せフォームの大枠を作っていきます。
このタグで送信ボタンを押した時のonFinishと、フォームでのバリデーション時に表示するvalidateMessagesを渡しています。また、スプレット構文のlayoutで、labelとwrapperのサイズの指定も行っています。

```TypeScript
function App() {

  const layout = {
    labelCol: {
      span: 8,
    },
    wrapperCol: {
      span: 8,
    },
  };

  const validateMessages = {
    required: '${label}は必須です。',
    types: {
      email: '${label}を正しい形式で入力してください。',
    },
    number: {
      range: '${label}は${min}から${max}の間で入力してください。',
    },
  };

  const onFinish = (item: string) => {
    console.log(item);
  };
```

```TSX
return (
  <Form {...layout} name="nest-messages" onFinish={onFinish} validateMessages={validateMessages}>
```

次にフォーム内の項目についてですが、
FormタグのItemで作っていきます。

labelは項目名で、
rulesのrequiredは、そのままですが必須チェックの有無となります。
入力欄に関してはInputタグを用います。


```TSX
  <Form.Item
    name={['user', 'name']}
    label="お名前"
    rules={[
      {
        required: true,
      },
    ]}
  >
    <Input />
  </Form.Item>
```

nameの指定を配列にすると、一つ目の要素は大枠のkeyとなり、
二つ目の要素に入力された値のkeyとなります。

以下がonFinishされた時のパラメータなのですが、
実際に確認すると分かりやすいですね。

```JSON
{
    "user": {
        "name": "田中 太郎",
        "gender": 1,
        "age": 30,
        "email": "test@gmail.com",
        "prefecture": "東京都",
        "introduction": "これはテスト"
    }
}
```

requiredがtrueなので、入力がない場合はvalidateMessagesで定義したメッセージが適用され
以下のような形で表示されます。
labelの定義がそのまま当てはまるので便利ですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/f7ce56a8-7cd9-0a07-283c-7b9286ef4ba3.png)

続いてラジオボタンの実装をしていきます。
Form.ItemタグのinitialValueで初期値を定義しています。
ラジオボタンは、Radio.Groupタグの内部にRadioタグを増やして実装していきます。

```TSX
  <Form.Item
    name={['user', 'gender']}
    label="性別"
    initialValue={1}
    >
    <Radio.Group name="radiogroup">
      <Radio value={1}>男性</Radio>
      <Radio value={2}>女性</Radio>
      <Radio value={3}>その他</Radio>
    </Radio.Group>
  </Form.Item>
```

ちなみに初期値の定義としてRadio.Groupタグで
defaultValueを指定するというやり方もあるのですが…

```TSX
<Radio.Group name="radiogroup" defaultValue={2}>
```

以下のメッセージのように推奨はされていないので、
初期値はinitialValueを使うようにしましょう。

```console
arning: [antd: Form.Item] `defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.
```

次は年齢の入力です。
年齢入力に関しては、カレンダーや西暦で入力させた方が良いかとは思いますが、
今回は例なのでInputNumberを使っていきます。

これも見たままですが、
rulesでtypeにnumberを指定し、最小値と最大値を決めています。


```TSX
  <Form.Item
    name={['user', 'age']}
    label="年齢"
    rules={[
      {
        type: 'number',
        min: 0,
        max: 150,
      },
    ]}
  >
    <InputNumber />
  </Form.Item>
```

エラーメッセージも以下のように表示されます。
こういう場合、そもそも期待値以外は入力できないようにするのがベターかと思いますが、
今回は例なので、悪しからず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/8408cf3a-a9cf-06d0-64da-f5cc360592c1.png)


メールアドレスに関しては、typeにemailを指定しています。
あとは他とほぼ変わりません。

```TSX
  <Form.Item
    name={['user', 'email']}
    label="メールアドレス"
    rules={[
      {
        type: 'email',
      },
    ]}
  >
    <Input />
  </Form.Item>
```

こちらもメールアドレスの形式に沿っていないと、
以下のようにエラーメッセージで怒られました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/19ef77e2-4bab-2e06-3f6b-ba59503d95b7.png)


入力項目の最後のお問合せ内容ですが、
こちらは文字をたくさん入力できるようにしたいので、InputタグのTextAreaを指定します。
これにより可変式のTextAreaが実装できます。

```TSX
  <Form.Item name={['user', 'introduction']} label="お問合せ内容">
    <Input.TextArea />
  </Form.Item>
```

最後にボタンですが、antdのButtonタグを使用して、
typeにprimary、htmlTypeはsubmitを指定します。
wrapperは先ほど定義したlayoutと合わせて8としています。

```TSX
  <Form.Item
    wrapperCol={{
      ...layout.wrapperCol,
      offset: 8,
    }}
  >
    <Button type="primary" htmlType="submit">
      送信
    </Button>

  </Form.Item>
```

## 完成

こちらが完成したソースコードになります。

```TSX:App.tsx
import React from 'react';
import { Button, Form, Input, InputNumber, Radio } from 'antd';
import 'antd/dist/antd.css';
import Title from 'antd/lib/typography/Title';

function App() {

  const layout = {
    labelCol: {
      span: 8,
    },
    wrapperCol: {
      span: 8,
    },
  };

  const validateMessages = {
    required: '${label}は必須です。',
    types: {
      email: '${label}!を正しい形式で入力してください。',
    },
    number: {
      range: '${label}は${min}から${max}の間で入力してください。',
    },
  };

  const onFinish = (item: string) => {
    console.log(item);
  };

return (
  <div style={{margin: "50px"}}>
    <div style={{textAlign: "center"}}>
      <Title>お問合せフォーム</Title>
    </div>

    <Form {...layout} name="nest-messages" onFinish={onFinish} validateMessages={validateMessages}>

      <Form.Item
        name={['user', 'name']}
        label="お名前"
        rules={[
          {
            required: true,
          },
        ]}
      >
        <Input />
      </Form.Item>

      <Form.Item
        name={['user', 'gender']}
        label="性別"
        initialValue={1}
        >
        <Radio.Group name="radiogroup">
          <Radio value={1}>男性</Radio>
          <Radio value={2}>女性</Radio>
          <Radio value={3}>その他</Radio>
        </Radio.Group>
      </Form.Item>

      <Form.Item
        name={['user', 'age']}
        label="年齢"
        rules={[
          {
            type: 'number',
            min: 0,
            max: 150,
          },
        ]}
      >
        <InputNumber />
      </Form.Item>

      <Form.Item
        name={['user', 'email']}
        label="メールアドレス"
        rules={[
          {
            type: 'email',
          },
        ]}
      >
        <Input />
      </Form.Item>

      <Form.Item name={['user', 'prefecture']} label="都道府県">
        <Input />
      </Form.Item>

      <Form.Item name={['user', 'introduction']} label="お問合せ内容">
        <Input.TextArea />
      </Form.Item>

      <Form.Item
        wrapperCol={{
          ...layout.wrapperCol,
          offset: 8,
        }}
      >
        <Button type="primary" htmlType="submit">
          送信
        </Button>

      </Form.Item>
    </Form>
  </div>
  );
}

export default App;

```



## まとめ

長くなりましたが、今回も簡単にお問合せフォームを実装できました。

ちなみに例えばですが、rulesのtypeは、Ant Design内のinterface.d.tsにて
RuleTypeで定義されたものが使用できるので詳しく知りたい方は、
定義の中身を読んでみてもいいかも知れません。

現状、Ant Designは日本語文献があまりないので、海外文献の翻訳か、
中身のコードを読まざるを得ないところは結構あります。(むしろ公式がサンプル集みたいな感じです)

ただ割と直感的に実装はできるので、
良かったら試してみてください。

## 参考

https://ant.design/docs/react/introduce