React Hook Form, MUI, react-number-formatを組み合わせた汎用入力部品を作る

投稿日: 4/23/2022

はじめに

昨今のReactのフォームライブラリではスタンダードになっているReact Hook Formと、これもReactのプロジェクトではよく用いられるMUIの各種入力部品を組み合わせて、汎用的に使用できる入力部品を作ってみた。テキストの入力においては純粋なテキスト (string型) の入力と、数値 (number型) の入力を分けたくなるのが常なので、MUIの TextField をベースに、MuiTextFieldMuiNumericField の2つを用意してこれらを区別した。また、数値の入力部品では入力値をフォーマットして表示したい場面も多いので、MuiNumericField にはreact-number-formatも組み込んで、入力値のフォーマットができるようにした。

執筆時の依存バージョン (最終更新2024/11/03)

  • React v18.2.0
  • MUI v6.1.6
  • React Hook Form v7.53.1
  • react-number-format v5.4.2

先に最終的なデモを貼っておく。

一連のファイルを置いたGitHubリポジトリは以下になる。

現状形になっている部品一覧

以降では今回のインターフェース設計の思想と、実装時に得られた雑多な情報について軽く書きとめおく。この汎用部品を使いたい方はすぐ後の「利用者側から見たインターフェース」で使い方の雰囲気だけさっと眺めてGitHubに置いた

を見てもらうのがいいと思う。

インターフェース

利用者側から見たインターフェース

インターフェースを紹介するために、一番シンプルな MuiTextField (string型のテキスト入力部品) の利用方法を紹介する。MuiTextField 以外の部品も同じインターフェイス設計になっているので、MuiTextField が分かれば他の部品も簡単に利用できるようになっている。

今回の汎用部品は型引数としてフォームオブジェクトの型を取るようにした。フォームの型はTypeScriptでReact Hook Formを使う場合は必ず定義する (そして useForm の型引数に渡す) ことになるので、同じものをそのまま渡せば良い。ここで型を渡すと、入力部品の name のPropsに渡した文字列が、ちゃんとフォームのkeyとして存在するかチェックされる。

MuiTextFieldの使用例
<MuiTextField<FormData> name='name' control={control} rules={{ required: '必須項目です。', maxLength: { value: 5, message: '5文字以内で入力してください。' } }} config={{ displayErrorMessage: true }} muiProps={{ textFieldProps: { label: '名前', fullWidth: true } }} />

上記の通り、Propsは大きく3つに分けて設計した。

  • React Hook Formで定義されているProps
    今回はこれを入力部品のベースのインターフェースとする
  • MUIの部品に対して定義されているProps
    今回はこれを muiProps としてまとめる
  • 部品の提供者が追加で提供する設定項目のProps
    今回はこれを config としてまとめる

上の例で言うと、name, control, rulesがReact Hook Form向けのインターフェイスである。今回の汎用部品ではこれを設計のベースにしているので、ネストしないPropsとして渡すことができる。このうち controlname はReact Hook Formによる制御において必須なので、Propsとしても必須にしている。一方で rules は必須ではなく、Zod等のschemaライブラリを使ってフォームのschemaを定義し、useForm にschemaを渡す形でもバリデーションが実現できるようになっている。勿論バリデーションなしでも良い。

提供者側から見たインターフェース

とにかくPropsが多いので、3つの大きなまとまりで分けて管理するようにした。一つ目は今回主役となっているReact Hook FormのProps、2つ目はMUIのComponentのProps、3つ目が部品を提供する側が独自で組み込みたいオプションを提供するためのPropsである。

React Hook FormのProps

React Hook Formのインターフェイスは UseControllerProps としてライブラリから提供されている。このPropsはインターフェースとして最表面に出ていて欲しい(ネストして欲しくない)ので、今回作る部品のPropsはこの型との交差型にすることにした。上記のサンプルで言うと、name, control, rulesはReact Hook Formが公開しているインターフェイスで定義されたPropsである。

MUI ComponentのProps

MUIのPropsは部品によっては複数必要になる。例えばAutocompleteではAutocomplete自体のPropsに加えて、内部に持つTextFieldのProps (フォームの幅やラベルなど) も渡せるようにしたい。よって、これらはオブジェクトに整理して、PropsのmuiPropsという口で渡せるようにした。React Hook FormのPropsとMUIのPropsは同名のものが多く衝突する可能性が高い(そして意図せず衝突するとバグる)ので、最初から分けてバグを生む可能性を排除した。

部品提供者が独自に提供する設定項目

提供者が独自で組み込みたいオプションを提供するためのPropsについても、MUIのPropsと同様、オブジェクトにまとめて、configという口で渡せるようにした。react-number-formatのProps (NumberFormatPropsBase) は意味的にはここに収まるのが良いと考えたので、MuiNumberField のconfigについては NumberFormatPropsBase との交差型になっている。