contact

blog-MainImage

Kabe-Log

2022-03-04

2022-3-6

React Hook Formを使ったフォームのコンポーネント化。 forwardRef「このrefをお前に預ける。俺の大切なrefだ…」

frontend,

React.js,

Library,

TypeScript

今回は、React Hook Formを使用したフォームをコンポーネント化する方法について書きます。

タイトルは、ワンピースの有名なシーンを真似しました。最近ジャンプ読めてない…。

React Hook Formとは

公式サイトから、

「シンプルかつ、拡張性のある、使い勝手の良いフォームバリデーションライブラリ」

Reactでinput要素の値を取得しようとすると、フォームそれぞれの値をstateで管理し、対応することが多いと思います。React経験者なら、setA(e.target.value)を何度も書いた覚えがあるのではないでしょうか。

React Hook Formを使用すると、ライブラリが持つuseFormというカスタムフックに参照したいrefを保持させることで、フォームの値を管理します。また、自身で実装しようとするとめんどくさいバリデーションも、簡単に実装することができます。

詳細な使い方に関しては、今回は割愛します。記事も豊富ですし、ドキュメントも比較的分かりやすいです。

React Hook Formの記述方法は以下の方法で、registerをinput要素、textarea要素に記述します

{...register(“value”)}

コンポーネントの作成

早速コンポーネント化して使いまわせるようにしていきます。 registerで管理したい値を変更したいので、親のコンポーネントでこのように呼び出すようにしたいですね。

<Form type={""} label={""} {...register("name", { required: "必須項目です"})} error={errors.name ? errors.name.message : undefined} /> // エラーの場合は、errors.name.messageにrequiredの値(この場合、「必須項目です」)が入る。

ちなみに、requiredオプションをtrueにすることで、その値を必須にするようバリデーションをかけることができ、任意の文字列を指定することで、その文字列をerrorsの持っているmessageプロパティに渡すことができます。

では、コンポーネントを作成していきます。Formという名前にしました。

type Props = { type: "text" | "email" | "number"; label: string; error?: string; defaultValue?: string; placeholder?: string; }; export const Form = (props: Props) => { return ( <> <p>{props.label}</p> <input style={{ display: "block", padding: "0.5rem", width: "100%", border: "solid 1px", }} defaultValue={props.defaultValue} placeholder={props.placeholder} type={props.type} /> {props.error && ( <span style={{ color: "red", fontSize: "1rem" }}>{props.error}</span> )} </> ); };

forwardRef1.png このままではボタンを押しても、registerが子コンポーネント内のどの要素を参照していいか(refを認識していいか)分からず動作しません。 親のコンポーネントでフォームに値を入れても、registerは管理したい要素が認識できず、「値が入ってないよ」と怒られてしまいます。

コンソールにerrorsを表示。input要素を取得できていないですね。 forwardRef3.png

これを解決するには、refを親のコンポーネントに認識させる必要があります。 そこで、forwardRefを使います。

forwardRefとは、

Reactの公式から、

ref のフォワーディングはあるコンポーネントを通じてその子コンポーネントのひとつに ref を自動的に渡すテクニックです。

正直言ってわかりにくいReactのドキュメントにしては、言いたいことはわかるかと思います笑。

つまり、forwardRefを使用し、親のコンポーネントからrefを渡すことで、子コンポーネント内の参照したい要素(この要素に指定したrefは親から渡ってきたもの)を指定することができます。

コンポーネントの修正

forwardRefでコンポーネントをラッピングします。 typeScriptの書き方はジェネリクスに<refを渡したい要素の型、propsの型>で指定します。

Formを以下に変更

import { forwardRef } from "react"; import type { UseFormRegisterReturn } from "react-hook-form"; type Props = UseFormRegisterReturn & { type: "text" | "email" | "number"; label: string; error?: string; defaultValue?: string; placeholder?: string; }; export const Form = forwardRef<HTMLInputElement, Props>((props, ref) => { return ( <div> <label>{props.label}</label> <input type={props.type} style={{ display: "block", padding: "0.5rem", width: "100%", border: "solid 1px", }} defaultValue={props.defaultValue} placeholder={props.placeholder} ref={ref} name={props.name} onChange={props.onChange} onBlur={props.onBlur} /> {props.error && ( <span style={{ color: "red", fontSize: "1rem" }}>{props.error}</span> )} </div> ); });

型Propsに、UseFormRegisterReturnを追加します。これはregisterが持つ型で、Name、 onChange、onBlur、 refはUseFormRegisterReturnのpropsです。 こちら公式のregisterのpropsについてのページです。

https://react-hook-form.com/api/useform/register/#registerRef

再度確認してみます。 forwardRef2.png

forwardRefで子コンポーネントの参照したいDOMにアクセスできるようになり、値を取得することができました。

コンソールでerrorsを表示します。 forwardRef4.png

refがinput要素を取得できていることが確認できますね。

まとめ

今回は説明が難しく、自分の文章化能力をもっと鍛える必要があるなと思いました。 少しでも、伝わっていればいいなと思います。 こちらが最終的なコードです。tailwind.cssを使ってしまっているので、styleは好きなように直してもらえればと思います。

import type { VFC } from "react"; import { forwardRef } from "react"; import type { SubmitHandler, UseFormRegisterReturn } from "react-hook-form"; import { useForm } from "react-hook-form"; import type { Inputs } from "src/interface/types"; type Props = UseFormRegisterReturn & { type: "text" | "email" | "number"; text: string; error?: string; placeholder?: string; defaultValue?: string | number; }; const Form = forwardRef<HTMLInputElement, Props>((props, ref) => { return ( <> <p>{props.text}</p> <input type={props.type} className="block p-1 w-full text-lg border" defaultValue={props.defaultValue} placeholder={props.placeholder} ref={ref} name={props.name} onChange={props.onChange} onBlur={props.onBlur} /> {props.error && ( <span className="text-sm text-red-500">{props.error}</span> )} </> ); }); const Home: VFC = () => { const { register, handleSubmit, formState: { errors }, } = useForm<Inputs>(); const onSubmit: SubmitHandler<Inputs> = (data) => { console.log(data); }; return ( <div className="p-4 my-20 mx-auto w-96"> <h1 className="text-3xl font-bold text-center">React-Hook-Form</h1> <form onSubmit={handleSubmit(onSubmit)}> <div className="my-4" /> <Form type="text" text="名前" placeholder="山田 太郎" error={errors.name ? errors.name.message : undefined} {...register("name", { required: "必須項目です", })} /> <div className="my-4" /> <Form type={"email"} text="メールアドレス" placeholder="example@example.com" error={errors.email ? errors.email.message : undefined} {...register("email", { required: "必須項目です" })} /> <input className="block px-3 my-4 mx-auto text-lg text-white bg-blue-500 hover:bg-blue-700 rounded-sm" type="submit" value="ログ出力" /> </form> </div> ); }; export default Home;

search

recommended

AWS資格 SAPとSOAを取得したので、対策や受験時の感想について
2022-05-23AWS資格 SAPとSOAを取得したので、対策や受験時の感想について
実務経験一年の転職活動(エンジニア生活:2月)
2022-03-15実務経験一年の転職活動(エンジニア生活:2月)
2022年は、フルスタックエンジニアに(エンジニア生活:1月)
2022-02-072022年は、フルスタックエンジニアに(エンジニア生活:1月)
「AWSエンジニア入門講座――学習ロードマップで体系的に学ぶ」を読んだ感想
2022-01-29「AWSエンジニア入門講座――学習ロードマップで体系的に学ぶ」を読んだ感想
エンジニア転職した一年の振り返り
2022-01-04エンジニア転職した一年の振り返り
Remixと朝活(エンジニア生活:11月)
2021-12-02Remixと朝活(エンジニア生活:11月)

contact