📑
React Hook Formで自作コンポーネントをバリデーションする
はじめに
備忘録です。
React Hook Form(以下RHF)を使えば、こんな感じで簡単にフォームのバリデーションを実装することができますよね。
※ ChakraUIを使ってます
<Input
id="name"
placeholder="name"
{...register("name", {
required: "required",
})}
/>;
これを自作のコンポーネントで行う方法です。
TL;DR
Controllerを使います。
実際にやってみる
適当にこんな感じのコンポーネントを作ってみます。
type Inputs = {
name: string;
};
export const TestForm = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<FormControl isInvalid={!!errors.name}>
<FormLabel htmlFor="name">名前</FormLabel>
<Input
id="name"
placeholder="name"
{...register("name", {
required: "required",
})}
/>
<FormErrorMessage>
{errors.name && errors.name.message}
</FormErrorMessage>
</FormControl>
<Button mt={4} colorScheme="teal" isLoading={isSubmitting} type="submit">
送信
</Button>
</form>
);
};
フォームが空のときはエラーで弾かれます。問題なさそうです。
次に、こんな感じでモーダルからチェックボックスを複数選択できるような自作コンポーネントの場合を考えてみます。
モーダルのコードはこんな感じ。
type HobbyModalProps = {
isOpen: boolean;
onClose: () => void;
};
const HobbyModal = (props: HobbyModalProps) => {
const hobbies = ["野球", "サッカー", "バスケ"];
const [checkedValues, setCheckedValues] = useState<string[]>([]);
const handleChange = (values: string[]) => {
setCheckedValues(values);
};
const handleSubmit = () => {
console.log(checkedValues);
props.onClose();
};
return (
<Modal isOpen={props.isOpen} onClose={props.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>選択</ModalHeader>
<ModalCloseButton />
<ModalBody>
<CheckboxGroup
onChange={(values) => handleChange(values as string[])}
>
<Stack>
{hobbies.map((hobby) => (
<Checkbox key={hobby} value={hobby}>
{hobby}
</Checkbox>
))}
</Stack>
</CheckboxGroup>
</ModalBody>
<ModalFooter>
<Button onClick={handleSubmit} colorScheme="blue" mr={3}>
完了
</Button>
<Button onClick={props.onClose}>キャンセル</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
まずcontrol
をuseFormから取り出して、
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
control,
} = useForm<Inputs>();
RHFのController
で自作コンポーネントをラップします。
今回はonChange
とvalue
だけを渡します。
※後述しますが、究極的にはonChange
だけあれば機能します。
<FormControl isInvalid={!!errors.hobby}>
<FormLabel htmlFor="hobby">趣味</FormLabel>
<Controller
control={control}
name="hobby"
rules={{ required: "required" }}
render={({ field: { onChange, value } }) => (
<>
<HobbyModal
isOpen={isOpen}
onClose={onClose}
onChange={onChange}
defaultValue={value}
/>
<Button id="hobby" onClick={onOpen}>選ぶ</Button>
</>
)}
/>
<FormErrorMessage>{errors.hobby && errors.hobby.message}</FormErrorMessage>
</FormControl>;
モーダルの方も併せて修正します。
type HobbyModalProps = {
isOpen: boolean;
onClose: () => void;
onChange: (value: string[]) => void;
defaultValue?: string[];
};
const HobbyModal = (props: HobbyModalProps) => {
const hobbies = ["野球", "サッカー", "バスケ"];
const [checkedValues, setCheckedValues] = useState<string[]>([]);
const handleChange = (values: string[]) => {
setCheckedValues(values);
};
const handleSubmit = () => {
props.onChange(checkedValues);
props.onClose();
};
return (
<Modal isOpen={props.isOpen} onClose={props.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>選択</ModalHeader>
<ModalCloseButton />
<ModalBody>
<CheckboxGroup
defaultValue={props.defaultValue}
onChange={(values) => handleChange(values as string[])}
>
<Stack>
{hobbies.map((hobby) => (
<Checkbox key={hobby} value={hobby}>
{hobby}
</Checkbox>
))}
</Stack>
</CheckboxGroup>
</ModalBody>
<ModalFooter>
<Button onClick={handleSubmit} colorScheme="blue" mr={3}>
完了
</Button>
<Button onClick={props.onClose}>キャンセル</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
無事バリデーションが効くようになりました。
ちなみに
render
の引数であるfieldは何なのかというと、
-
OnChange
: RHFに値を送る関数。一番重要。 -
onBlur
: RHFのmodeがonBlurのときに使う...?(使ったことない…) -
value
: 現在RHFが管理している値の状態。 -
ref
: エラーのときにそのフォームにフォーカスする時とかに使うDOMへの参照。 -
name
: Controllerに渡すnameと同じ。コンポーネントによっては必要になる
なので、必要がなければonChange
だけ渡せばOK。
Discussion