React Hook Form × Chakra UIで作るEFOを考慮したフォーム
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(エントリーフォーム最適化)として意識したいのは下記のような部分です。
- 必須項目を埋めずに次の項目へ移動するとエラー文言を表示
- メールアドレス形式のチェック
- 電話番号の桁数指定(日本では10桁か11桁のみ)。
- 全てのバリデーションが通るまでSubmitボタンを無効化しておく
- (おまけ)郵便番号を入力すると都道府県+住所を自動で補完
今回の成果物は下記に。
①必須項目を埋めずに次の項目へ移動するとエラー文言を表示
デフォルトだとSubmitボタンを押した時(onSubmit)しかバリデーションが走らないため、
const { register, errors, handleSubmit, formState } = useForm();
useFormのオプションを使います。
const { register, errors, handleSubmit, formState } = useForm({
mode: "all",
});
②メールアドレス形式のチェック
<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桁のみ)。
<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文字以下で入力してください。」と表示されます。
④全てのバリデーションが通るまでSubmitボタンを無効化しておく
<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オブジェクトについてはこちら。
{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>
を読み込んでおきます。
<!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>タグを追加します。
<form
onSubmit={handleSubmit(onSubmit)}
className="h-adr"
>
<span className="p-country-name" style={{ display: "none" }}>Japan</span>
47都道府県用の配列を用意し、
// 都道府県配列
const selectArray2 = ["北海道", "青森県", "岩手県",...];
都道府県用の<Select>にclass名「p-region」を付与、ご住所用の<Input>にclass名「p-locality p-street-address p-extended-address」を付与すれば完了です。
<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
Discussion
zodでバリデーション入れながらデモを作ってみました。
簡単ですが、以上です。