React Hook Form × Chakra UIで作るEFOを考慮したフォーム

7 min読了の目安(約6700字TECH技術記事

Reactでフォームを作成する際に使いやすいライブラリ「React Hook Form」を使ったフォームを、最近流行りの「Chakra UI」を使って作成したいと思います。

DEMO

React Hook Form

使いやすさに重点を置いた、React用の高性能なフォームバリデーションライブラリ。
同様のライブラリ「Formik」や「Redux-Form」に比べ、コンポーネントの再レンダリングが少なく、パフォーマンスに優れた作りとなっています。

Chakra UI

2021/01/05現在、スター数13.9kと、急成長中のReact UIコンポーネントライブラリ。
あらかじめ多くのデザインパーツが揃っているので、すぐにReactアプリに導入が可能です。
WAI-ARIA準拠の設計で、アクセシビリティにも配慮されています。

下記のように必要なコンポーネントを呼び出して使います。

import { FormControl, FormLabel, Input, Button } from "@chakra-ui/react";

EFO要件

EFO(エントリーフォーム最適化)として意識したいのは下記のような部分です。

  1. 必須項目を埋めずに次の項目へ移動するとエラー文言を表示
  2. メールアドレス形式のチェック
  3. 電話番号の桁数指定(日本では10桁か11桁のみ)。
  4. 全てのバリデーションが通るまでSubmitボタンを無効化しておく
  5. (おまけ)郵便番号を入力すると都道府県+住所を自動で補完

今回の成果物は下記に。

①必須項目を埋めずに次の項目へ移動するとエラー文言を表示

デフォルトだとSubmitボタンを押した時(onSubmit)しかバリデーションが走らないため、

src/index.tsx
const { register, errors, handleSubmit, formState } = useForm();

useFormのオプションを使います。

src/index.tsx
const { register, errors, handleSubmit, formState } = useForm({
  mode: "all",
});

onChange」では入力するたびにバリデーションが走りますが、入力項目に入り、何も入力せずにその項目を出た際にバリデーションが走りません。

逆に「onBlur」では入力項目に入り、何も入力せずにその項目を出た際にエラー文言は出せますが、入力内容が正しくなった際にバリデーションが走りません。

ですが「all」は上記両方のイベントにてバリデーションをさせることが可能ですので、今回のニーズに合う設定となります。

②メールアドレス形式のチェック

src/index.tsx
<FormControl
  id="inputText2"
  isRequired
  isInvalid={errors.inputText2 ? true : false}
>
  <FormLabel>メールアドレス</FormLabel>
  <Input
    name="inputText2"
    placeholder="test@example.com"
    ref={register({
      required: "メールアドレスは必須です。",
      pattern: {
        value: /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
        message: "メールアドレス形式で入力してください。",
      },
    })}
  />
  <FormErrorMessage>
    {errors.inputText2 && errors.inputText2.message}
  </FormErrorMessage>
</FormControl>

required … 必須項目が空の際に「メールアドレスは必須です。」と表示されます。
pattern … patternのvalue(ここではメールアドレスの正規表現)を満たさない場合、「メールアドレス形式で入力してください。」と表示されます。

③電話番号の桁数指定(日本では10桁か11桁のみ)。

src/index.tsx
<FormControl
  id="inputText3"
  isRequired
  isInvalid={errors.inputText3 ? true : false}
>
  <FormLabel>電話番号</FormLabel>
  <Input
    type="tel"
    name="inputText3"
    placeholder="0123456789"
    ref={register({
      required: "電話番号は必須です。",
      minLength: {
        value: 10,
        message: "10文字以上入力してください。",
      },
      maxLength: {
        value: 11,
        message: "11文字以下で入力してください。",
      },
    })}
    maxLength={11}
  />
  <FormErrorMessage>
    {errors.inputText3 && errors.inputText3.message}
  </FormErrorMessage>
</FormControl>

minLength … valueより小さい整数の場合「10文字以上入力してください。」と表示されます。
maxLength … valueより大きい整数の場合「11文字以下で入力してください。」と表示されます。

maxLength={11}の箇所はHTML属性のmaxlengthです。
ちなみに<input type="number">ではこの属性は効かないため、<input type="tel">を採用しています。
また、11桁に制限しているので「React Hook Form」での【maxLength】は不要なのでは?と思うかもしれませんが、全角数字を入れようとした際に下記のようになるケースがあるため設定しておきます。

④全てのバリデーションが通るまでSubmitボタンを無効化しておく

src/index.tsx
<Button
  type="submit"
  colorScheme="blue"
  disabled={!formState.isValid}
  isLoading={formState.isSubmitting}
>
  送信する
</Button>

formState.isValidについて

すべての項目のバリデーションが通ると【formState.isValid】がtrueとなります。
これを利用して「disabled」属性をON/OFFしています。

formState.isSubmittingについて

Chakra UIの<Button>属性に「isLoading」というものがあり、ボタン内部にローディングを表示することが出来ます。
フォームの送信中は【formState.isSubmitting】がtrueになり、送信完了するとfalseに戻ります。それを利用して「isLoading」属性を付与しています。

formState.isSubmittedについて

送信された場合に【formState.isSubmitted】がtrueになります(フォームの送信が成功したではなく送信したかのフラグ)。
formStateオブジェクトについてはこちら

src/index.tsx
{formState.isSubmitted ? (
  <Alert status="success">
    <AlertIcon />
    送信完了しました。
  </Alert>
) : (
  <>
    <Alert status="info">
      <AlertIcon />
      下記を入力して下さい。
    </Alert>
    <form onSubmit={handleSubmit(onSubmit)}>...</form>
  </>
)}

よって、上記のようにすることで、フォーム送信後にフォーム自体を消し、送信完了した旨を表示することが出来ます。
この辺りの制御も自前でStateの管理をすること無く、React Hook Formがやってくれるので楽ですね。

⑤(おまけ)郵便番号を入力すると都道府県+住所を自動で補完

YubinBangoライブラリ(ajaxzip3の開発者による正式な後継)を使います。


<script src="https://yubinbango.github.io/yubinbango/yubinbango.js"></script>を読み込んでおきます。
build/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React Hook Form × Chakra UI</title>
</head>
<body>
<div id="root"></div>
<script src="https://yubinbango.github.io/yubinbango/yubinbango.js"></script>
<script src="bundle.js"></script>
</body>
</html>

<form>タグにはclass名「h-adr」を付与、非表示化したclass名「p-country-name」の<span>タグを追加します。

src/index.tsx
<form
  onSubmit={handleSubmit(onSubmit)}
  className="h-adr"
>
  <span className="p-country-name" style={{ display: "none" }}>Japan</span>

47都道府県用の配列を用意し、

src/index.tsx
// 都道府県配列
const selectArray2 = ["北海道", "青森県", "岩手県",...];

都道府県用の<Select>にclass名「p-region」を付与、ご住所用の<Input>にclass名「p-locality p-street-address p-extended-address」を付与すれば完了です。

src/index.tsx
<FormControl id="selectBox2">
  <FormLabel>都道府県</FormLabel>
  <Select
    name="selectBox2"
    placeholder="---"
    ref={register()}
    className="p-region"
  >
    {selectArray2.map((val, i) => {
      return (
        <option key={i} value={val}>
          {val}
        </option>
      );
    })}
  </Select>
  <FormErrorMessage>
    {errors.selectBox2 && errors.selectBox2.message}
  </FormErrorMessage>
</FormControl>
<FormControl id="inputText5">
  <FormLabel htmlFor="inputText5">ご住所</FormLabel>
  <Input
    name="inputText5"
    placeholder="東京都千代田区千代田1-1"
    ref={register()}
    className="p-locality p-street-address p-extended-address"
  />
</FormControl>

参考

React Hook Form関連

ホーム | React Hook Form - Simple React forms validation

Chakra UI関連

Chakra UI - A simple, modular and accessible component library that gives you the building blocks you need to build your React applications. - Chakra UI

Chakra UI ではじめる Tailwind CSS へのささやかな抵抗 / Modest resistance to Tailwind CSS starting with Chakra UI - Speaker Deck