registerでは動かない?React Hook FormとMUIを正しく連携させる方法
React Hook Form(以下RHF)を勉強したあと、MUIなどのUIライブラリと組み合わせようとしたときに、急にハードルが上がったと感じたことはありませんか?
「registerを使ったフォームは動くのに、MUIのTextFieldやSelectで急にエラーが出る」「Controllerって何?」
そんな疑問を持った方向けに、この記事ではRHFとMUIを組み合わせるときの基本と実装パターンを整理します。
RHFの基本:registerで値を取得する
まずはRHFの基本から。
RHFの基本的な使い方は、registerでフォーム項目を登録し、handleSubmitで送信処理を行うという流れです。
HTMLの標準inputタグなら、以下のように非常にシンプルに書けます。
import { useForm } from "react-hook-form";
function BasicForm() {
const { register, handleSubmit } = useForm();
// 送信時に実行する関数
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="名前" />
<button type="submit">送信</button>
</form>
);
}
export default BasicForm;
これで、フォームの入力値がconsole.logに出力されます。
MUIコンポーネントで試すと動かない?
ところが、次にMUIのTextFieldやSelectを使おうとすると、途端に混乱が始まります。
<TextField label="名前" {...register("name")} />
これ自体は動く場合もありますが、SelectやCheckboxになると一気に挙動がおかしくなります。
なぜでしょうか?
理由は、RHFとMUIでフォーム値の管理の仕組みが異なるからです。
- RHFの
registerは「非制御コンポーネント(Uncontrolled)」として、DOMを直接参照して値を取得します。 - 一方、MUIの
TextFieldやSelectは「制御コンポーネント(Controlled)」で、valueとonChangeをpropsで受け取り、内部で状態を持ちません。
この「非制御 vs 制御」の違いが原因で、RHFのregisterだけではMUIコンポーネントを正しく制御できないのです。
Controllerで解決する
このギャップを埋めるのが、RHFのControllerです。
Controllerを使うと、RHFがvalueとonChangeを制御し、MUIコンポーネントと自然に連携できるようになります。
例:TextFieldとSelectをControllerで扱う
import { useForm, Controller } from "react-hook-form";
import { TextField, MenuItem, Select } from "@mui/material";
function MuiForm() {
const { control, handleSubmit } = useForm({
defaultValues: { name: "", age: 20 },
});
return (
<form onSubmit={handleSubmit(console.log)}>
<Controller
name="name"
control={control}
render={({ field }) => (
<TextField label="名前" {...field} />
)}
/>
<Controller
name="age"
control={control}
render={({ field }) => (
<Select
{...field}
>
<MenuItem value={10}>10歳</MenuItem>
<MenuItem value={20}>20歳</MenuItem>
<MenuItem value={30}>30歳</MenuItem>
</Select>
)}
/>
<button type="submit">送信</button>
</form>
);
}
render関数の中でfieldを展開し、そのままMUIコンポーネントに渡すのが基本形です。
fieldにはvalue, onChange, onBlur, refなどが含まれており、これを渡すだけでRHFとMUIが連動します。
最初は少し冗長に見えるかもしれませんが、慣れると「この形にすれば動く」とすぐ理解できるようになります。
よくある構成パターン
RHF + MUIでよく使われるパターンをまとめておきます。
✅ TextField
<Controller
name="email"
control={control}
rules={{ required: "必須項目です" }}
render={({ field, fieldState }) => (
<TextField
label="メールアドレス"
{...field}
// エラー時の動きをここで定義する
error={!!fieldState.error}
helperText={fieldState.error?.message}
/>
)}
/>
✅ Checkbox
<Controller
name="agree"
control={control}
rules={{ required: true }}
render={({ field }) => (
<FormControlLabel
control={<Checkbox {...field} checked={field.value} />}
label="利用規約に同意する"
/>
)}
/>
field.valueやcheckedの扱いを合わせるのがポイントです。
Controllerを使うと何が良いのか?
| 項目 | register | Controller |
|---|---|---|
| コンポーネント | inputなどのシンプル要素 | MUIの複雑なUI |
| 値の管理 | DOMから取得 | Reactのstate制御 |
| 柔軟性 | シンプルだが制限あり | 高い(制御・加工が容易) |
| バリデーション |
rulesでは不可 |
rulesで柔軟に設定可能 |
Controllerを使うことで、MUIコンポーネントの特性に合わせつつ、RHFの強み(再レンダーが少ない・バリデーションが簡単)を活かせます。
まとめ
- RHFの
registerは、非制御コンポーネント向けの仕組み。 - MUIのコンポーネントは制御コンポーネントなので、そのままでは動作しない。
-
Controllerを使えば、MUIとの組み合わせがスムーズに動作する。 - 最初はテンプレートをコピーして覚えるのが近道。
慣れてくると、MUIとRHFの相性はとても良いと感じるはずです。
フォームの安定動作と保守性の高い実装を両立できるようになるので、ぜひこの組み合わせに慣れておきましょう。
Discussion