昨今のReactのフォームライブラリではスタンダードになっているReact Hook Form (v7) と、これもReactのプロジェクトではよく用いられるMUI (v5)の各種入力部品を組み合わせて、汎用的に使用できる入力部品を作ってみた。テキストの入力においては純粋なテキスト (string型) の入力と、数値 (number型) の入力を分けたくなるのが常なので、MUIの TextField
をベースに、MuiTextField
と MuiNumericField
の2つを用意してこれらを区別した。また、数値の入力部品では入力値をフォーマットして表示したい場面も多いので、MuiNumericField
にはreact-number-formatも組み込んで、入力値のフォーマットができるようにした。
執筆時の依存バージョン (最終更新2022/12/17)
- React v18.2.0
- MUI v5.8.4
- React Hook Form v7.30.0
- react-number-format v5.1.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つに分けて設計した。
muiProps
としてまとめるconfig
としてまとめる上の例で言うと、name
, control
, rules
がReact Hook Form向けのインターフェイスである。今回の汎用部品ではこれを設計のベースにしているので、ネストしないPropsとして渡すことができる。このうち control
と name
は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のインターフェイスは UseControllerProps
としてライブラリから提供されている。このPropsはインターフェースとして最表面に出ていて欲しい(ネストして欲しくない)ので、今回作る部品のPropsはこの型との交差型にすることにした。上記のサンプルで言うと、name
, control
, rules
はReact Hook Formが公開しているインターフェイスで定義された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
との交差型になっている。