はじめに
現在取り組んでいる、学習記録アプリのリニューアル作業にて登録フォームのバリデーションにreact-hook-formを導入することにしました。学んだことを備忘録としてまとめてみました。
実現したいこと
フォームの登録ボタンを押したときに、バリデーションを適用させたい
react-hook-formとは?
TOPページより引用(日本語訳)
"Performant, flexible and extensible forms with easy-to-use validation."
(高性能で柔軟性があり拡張可能なフォームで、使いやすいバリデーション機能を備えています)
Reactでフォームを取り扱う際に使える高性能なライブラリと理解しました。
導入手順
1.react-hook-formのドキュメントを確認
2.ライブラリをインストール
$ npm install react-hook-form
3.Quick StartのExampleを確認
react-hook-formのドキュメントより引用
4.form形式へ変更
クイックスタートを確認すると、formタグを使用した構造を元に使用例が提示されていました。
私は各入力値を、useStateで設定するように実装していたため、form形式に変更することから始めました。
formの形式に修正
<Modal initialFocusRef={initialRef} isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
+ <form onSubmit={handleSubmit(onRecordRegist)}>
<ModalHeader>Modal Title</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl isRequired>
<FormLabel>学習内容</FormLabel>
<Input
ref={initialRef}
placeholder="学習内容"
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setTitle(e.target.value) // useStateを使い学習内容をセット
}
/>
</FormControl>
<FormControl isRequired>
<FormLabel>学習時間</FormLabel>
<NumberInput
defaultValue={0}
max={50}
min={0}
onChange={(_, valueAsNumber: number) =>
setTime(valueAsNumber) // useStateを使い学習時間をセット
}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</FormControl>
</ModalBody>
<ModalFooter>
- <Button colorScheme="teal" onClick={onRecordRegist}>
+ <Button colorScheme="teal" type="submit">
登録
</Button>
</ModalFooter>
+ </form>
</ModalContent>
</Modal>
5.handleSubmitの導入
ドキュメントのexampleに倣って、<form onSubmit={handleSubmit(onRecordRegist)}>
を記載しました。
公式ドキュメントより引用
This function will receive the form data if form validation is successful.
日本語訳:この関数は、フォームのバリデーションが成功した場合にフォームデータを受け取ります。
handleSubmitの引数に指定した、関数(onRecordRegist)はバリデーションが成功した場合、 onRecordRegistを実行
します。
また、data
にはフォームから受け取ったデータを取得できます。
useStateで設定していた処理は削除しました。
reac-hook-formを使用するために、カスタムフックのuseForm
を追加しました。
useFormを使うことで、フォームの管理が可能になります。
import { ChangeEvent, useEffect, useState } from "react";
import { addStudyRecord, GetAllStudyRecords } from "./lib/study-record";
import { Record } from "./domain/record";
import { BsPencil } from "react-icons/bs";
import React from "react";
+ import { useForm, SubmitHandler, Controller } from "react-hook-form";
function App() {
const [studyRecords, setStudyRecords] = useState<Record[]>([]);
const [isLoading, setIsLoading] = useState(true);
const { isOpen, onOpen, onClose } = useDisclosure();
- const initialRef = React.useRef(null);
- const [title, setTitle] = useState(""); // useStateを使い学習内容をセットしていた処理を削除
- const [time, setTime] = useState(0); // useStateを使い学習時間をセットしていた処理を削除
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ setValue,
+ control,
+ } = useForm<Partial<Record>>()
const getAllStudyRecords = async () => {
const records = await GetAllStudyRecords();
setStudyRecords(records);
setIsLoading(false);
};
useEffect(() => {
getAllStudyRecords();
}, []);
- const onRecordRegist = async () => {
+ const onRecordRegist: SubmitHandler<Partial<Record>> = async (data) => {
const insertData: Partial<Record> = {};
+ insertData.title = data.title;
+ insertData.time = data.time;
- insertData.title = title;
- insertData.time = time;
await addStudyRecord(insertData);
getAllStudyRecords();
onClose();
};
6.registerの導入
学習内容欄(title)にregister
を導入し、バリデーションを追加しました。
公式ドキュメントより引用
This method allows you to register an input or select element and apply validation rules to React Hook Form. Validation rules are all based on the HTML standard and also allow for custom validation methods.
日本語訳:このメソッドでは、input または select 要素を登録し、React Hook Form にバリデーションルールを適用できます。バリデーションルールはすべて HTML 標準に基づいており、カスタムバリデーションも可能です。
<ModalBody>
<FormControl>
<FormLabel>学習内容</FormLabel>
<Input
{...register("title", { required: true })}
placeholder="学習内容"
/>
{/* エラーの表示 */}
{errors.title?.type === "required" && (
<p style={{ color: "red" }}>学習内容は必須です</p>
)}
</FormControl>
</ModalBody>
{...register("title", { required: true })}
-
"title"
:登録するフィールドの名前 -
{ required: true }
:必須項目のバリデーションルールを設定 -
…
:JavaScriptのスプレッド構文、registerが生成した属性を展開
<Input
{...register("title", { required: true })}
placeholder="学習内容"
/>
{/* 実際はこのように展開されている */}
<Input
name="title"
onChange={[Function]}
onBlur={[Function]}
ref={[Function]}
placeholder="学習内容"
/>
7.Controllerの導入
勉強時間の入力欄にはChakraUIのNumberInputを使用していました。
ChakraUIのNumberInputなどの複雑なinput要素にはregisterが使えないため、Controller
を導入しました。
register
は標準のHTMLフォーム要素(\<input>など)に直接適用するように設計
されていますが、ChakuraUIのNumberInputなど複数の要素から成り立っているカスタムコンポーネント
には、registerは使用できません。
<Controller
name="time" // フォーム内での識別名
control={control} // React Hook Formのコントロールオブジェクト
rules={{ required: true, min: 0 }} // バリデーションルール
render={({ field }) => ( // レンダリング関数
<NumberInput
value={field.value} // Controllerから受け取った値、「現在の値」(フォームの状態)
onChange={(valueString) => { // 「新しく入力された値」(変更イベントの引数)、変更があると文字列として受け取る
field.onChange(parseInt(valueString)); // 文字列を数値に変換してからフォームに反映
}}
>
<NumberInputField />
{/* エラーの表示 */}
{errors.time?.type === "required" && (
<p style={{ color: "red" }}>学習時間は必須です</p>
)}
{errors.time?.type === "min" && (
<p style={{ color: "red" }}>
時間は0以上である必要があります
</p>
)}
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
)}
/>
name
:登録するフィールドの名称
rules
:必須項目、最小値=0のバリデーションルールを設定
render
:コントローラーから受け取った値を元に、フォームに設定
導入完了
おわりに
react-hook-formを使うことで、容易にバリデーション、フォームデータを取り扱えることを学びました。
ドキュメントを参照しつつ、自分のプロダクトに取り込むコツが少しずつ掴めてきたと感じています。
参考