RHFとMUIを併用する時はreact-hook-form-muiを使ってみよう!
はじめに
個人的に、RHFとMUIを組み合わせて使うとコード量が多くなりがちで、あまり好きではありませんでした。
Reactの開発に携われば、RHFやMUIに触れる機会は多いと思うので、中には共感してくれる方もいらっしゃるのではないでしょうか?
そんな中で見つけたのが、react-hook-form-muiというライブラリです。
このライブラリを使うと、RHFとMUIの連携を非常にシンプルに記述できるようになり、煩雑だったコードが驚くほどスッキリしました。
この記事では、react-hook-form-muiを使うメリットや、実際の利用方法について具体例を交えながら紹介していきます!💫
react-hook-form-muiとは
React Hook Form(以下RHF)とMaterial-UI(以下MUI)を組み合わせたシンプルなライブラリです。
このライブラリを利用することで、RHFとMUIを統合した使いやすいコンポーネントを手軽に利用できます。
技術スタック
reactのバージョンが17以上19未満でしか、react-hook-form-muiを使うことができないので、reactのバージョンは18に設定しました。
ライブラリ/ツール | バージョン |
---|---|
Next.js | 15.0.4 |
React | 18 |
react-hook-form-mui | 7.4.1 |
react-hook-form | 7.54.0 |
@emotion/react | 11.14.0 |
@emotion/styled | 11.14.0 |
@mui/material | 6.1.10 |
@mui/icons-material | 6.1.10 |
@mui/x-date-pickers | 7.23.1 |
環境構築
下記コマンドを実行するだけです!
npm install react-hook-form react-hook-form-mui
npm install @mui/material @emotion/react @emotion/styled
また、必要であれば@mui/icons-materialと@mui/x-date-pickersをinstallしてください。
npm install @mui/icons-material @mui/x-date-pickers
従来のRHFでMUIを使用する場合
RHFはDOM要素自体(ref
)によって値を管理することで、フォーム操作時の不要なレンダリングを抑える点が特徴です。
このような値の状態管理を行うコンポーネントは Uncontrolled Components と呼ばれます。
一方で、Reactは Controlled Components をベースに設計されています。
Controlled Componentsはstate
で値を管理する仕組みで、MUIもReact UI toolsと謳っているくらいなので、この設計思想に基づいています(ただし、一部のコンポーネントはUncontrolled Componentsとして扱うことも可能です)。
そのため、RHFではControlled ComponentsであるMUI(および他の外部コンポーネント)を連携させるために、Controller
を提供しています。
"use client";
import { Button, Stack, TextField } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
export const Form = () => {
const { control, handleSubmit } = useForm<{ name: string }>({
defaultValues: { name: "" },
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))} noValidate>
<Stack direction="column" width="300px" gap={5} margin={5}>
<Controller
name="name"
control={control}
defaultValue=""
rules={{
required: "This field is required",
}}
render={({ field, formState: { errors } }) => (
<TextField
{...field}
label="Name"
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</Stack>
</form>
);
};
react-hook-form-muiを使用した場合
上記のようにController
とTextField
を組み合わせて記述していた箇所が、react-hook-form-muiを使うことで、TextFieldElement
だけで完結します!
記述量が大幅に削減できているので、可読性がかなり良くなっています。
"use client";
import { Button, Stack } from "@mui/material";
import { TextFieldElement, useForm } from "react-hook-form-mui";
export const TypeSafeForm = () => {
const { control, handleSubmit } = useForm<{ name: string }>({
defaultValues: { name: "" },
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))} noValidate>
<Stack direction="column" width="300px" gap={5} margin={5}>
// ControllerとTextFiledは不要!
<TextFieldElement name="name" label="Name" required control={control} />
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</Stack>
</form>
);
};
たったこれだけで、MUIとRHFをスムーズに統合できるのは驚きですね。
複雑なフォームでも簡単に実装できそう。
さらに、型を明示的に扱う必要がない場合はuseForm
すら不要になります!
"use client";
import { Button, Stack } from "@mui/material";
import {
FormContainer,
TextFieldElement,
} from "react-hook-form-mui";
export const Form = () => {
const onSubmit = (data: { name: string }) => {
console.log(data);
};
return (
<FormContainer defaultValues={{ name: "" }} onSuccess={onSubmit}>
<Stack direction={"column"} width={"300px"} gap={5} margin={5}>
<TextFieldElement name="name" label="Name" required />
<Button type={"submit"} variant={"contained"} color={"primary"}>
Submit
</Button>
</Stack>
</FormContainer>
);
};
このように、FormContainer
を使えば、フォームの状態管理がバックグラウンドで処理されるため、コードがさらにスッキリします。
また、TextFieldElement
だけでなく以下のコンポーネントを使用することができます
- AutocompleteElement
- TextFieldElement
- SelectElement
- MultiSelectElement
- RadioButtonGroup
- CheckboxButtonGroup
- CheckboxElement
- SwitchElement
- PasswordElement
- DatePickerElement
- DateTimePickerElement
- SliderElement
- ToggleButtonGroupElement
以下は、DatePickerElement
を使用した実装例です。
import { useForm } from "react-hook-form-mui";
import { DatePickerElement } from "react-hook-form-mui/date-pickers";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
import { ja } from "date-fns/locale/ja";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { Button, Stack } from "@mui/material";
export const TypeSafeDatePickersForm = () => {
const { control, handleSubmit } = useForm<{ date: Date }>({
defaultValues: { date: new Date() },
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))} noValidate>
<Stack direction="column" width="300px" gap={5} margin={5}>
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}>
<DatePickerElement
name="date"
label="Date"
required
control={control}
/>
</LocalizationProvider>
<Button type="submit" variant="contained" color="primary">
Submit
</Button>
</Stack>
</form>
);
};
この例では、DatePickerElement
を利用し、ローカライズ設定をLocalizationProvider
で追加しています。
react-hook-form-mui
を活用することで、多種多様なフォームコンポーネントを簡潔に扱えるため、柔軟かつ効率的なフォーム開発が可能になります。
パフォーマンス面
先ほども述べた通り、RHFでは値の管理をDOM要素自体(ref)が行うため、フォームの操作時に不要なレンダリングを抑える点が売りです。
しかし、Controller
でラップをするとControlled Components扱いとなるので、値が入力されるたびに再レンダリングが発生します。
では、react-hook-form-muiのコンポーネントではどうなのかというと、
値が入力されるたびに再レンダリングされてますね、内部でControllerでラップしているのでしょう。
終わりに
React19ではforwardRef
を介さずにref
をコンポーネントに渡すことができるようになりました。
ref
の扱いがよりシンプルになったため、「もしかして、MUIも対応してくれているのでは?」と密かに期待していました。
そこで、試しにReact 19でMUIのSelect
コンポーネントにそのままregister
を渡してみました。
結果はどうだったかというと…エラーが出てしまいました。
ま、ともかくRHFとMUIを併用するときはreact-hook-form-muiがオススメです!というお話でした。
参考
Discussion