React Hook Formでコンポーネントを分割する方法
こんにちは!
スマレジ テックファームのMichiです!
今回はタイトルの通り、React Hook Formでコンポーネントを分割する方法をご紹介します。
解説
サンプル
サンプルとして、 メールアドレス、ユーザー名、パスワードを入力するシンプルな登録フォームを用意しました。それぞれのフィールドには、React Hook Form で必須入力のバリデーションをかけています。
SignUp.tsx
import { useForm } from "react-hook-form"; type FormData = { email: string; username: string; password: string; }; export const SignUp = () => { const { register, handleSubmit, formState: { errors } } = useForm<FormData>(); const onSubmit = (data: FormData) => console.log(data); return ( <> <h1>登録フォーム</h1> <form onSubmit={handleSubmit(onSubmit)}> <div className="field"> <label htmlFor={"email"}>メールアドレス</label> <input id="email" {...register("email", { required: true })} /> {errors.email && ( <span className="error">このフィールドは必須です</span> )} </div> <div className="field"> <label htmlFor={"username"}>ユーザー名</label> <input id="username" {...register("username", { required: true })} /> {errors.username && ( <span className="error">このフィールドは必須です</span> )} </div> <div className="field"> <label htmlFor={"password"}>パスワード</label> <input id="password" {...register("password", { required: true })} type="password" /> {errors.password && ( <span className="error">このフィールドは必須です</span> )} </div> <button className="button" type="submit"> 登録 </button> </form> </> ); };
これら3つのフィールドは同じ作りなので、別コンポーネントに切り出して抽象化したいです。
抽象化
まず、SignUp.tsx
を次のように修正します。
SignUp.tsx
import { FormProvider, useForm } from "react-hook-form"; import { InputField } from "./InputField"; export type FormData = { email: string; username: string; password: string; }; export const SignUp = () => { const methods = useForm<FormData>(); const onSubmit = (data: FormData) => console.log(data); return ( <> <h1>登録フォーム</h1> <FormProvider {...methods}> <form onSubmit={methods.handleSubmit(onSubmit)}> <InputField name="email" labelText="メールアドレス" /> <InputField name="username" labelText="ユーザー名" /> <InputField name="password" labelText="パスワード" type="password" /> <button className="button" type="submit"> 登録 </button> </form> </FormProvider> </> ); };
InputField
は各フィールドを抽象化したコンポーネントで、次に説明します。
ここでのポイントは、FormProvider
でフォーム全体をラップすることです。propsには、useForm
から取り出したmethods
を丸ごと渡します。
こうすることで、各InputField
へ個別にpropsを渡さなくても、それぞれのコンポーネントでmethods
を利用できるようになります。
次に、InputField.tsx
の実装を確認します。
InputField.tsx
import { useFormContext } from "react-hook-form"; type InputFieldProps = { name: string; labelText: string; } & JSX.IntrinsicElements["input"]; export const InputField = ({ name, labelText, ...others }: InputFieldProps) => { const { register, formState: { errors } } = useFormContext(); return ( <div className="field"> <label htmlFor={name}>{labelText}</label> <input id={name} {...register(name, { required: true })} {...others} /> {errors[name] && <span className="error">このフィールドは必須です</span>} </div> ); };
先ほど、親コンポーネントからFormProvider
でmethods
を受け取っているので、 組み込み関数のuseFormContext
を呼び出すだけで、methods
の各プロパティが利用できます。
あとは、<label>
と<input>
要素に適用していくだけです。
propsのothers
には、JSX.IntrinsicElements["input"]
の各属性が入るので、親から属性値を渡すことで<input>
フォームをカスタマイズできます。
おわりに
React Hook Formでコンポーネントを分割する方法をご紹介しました。
React Hook Formは便利なライブラリですが、学習コストがなかなか高いので、しっかり使いこなすには勉強が必要だなと思いました。