🍇

registerでは動かない?React Hook FormとMUIを正しく連携させる方法

に公開

React Hook Form(以下RHF)を勉強したあと、MUIなどのUIライブラリと組み合わせようとしたときに、急にハードルが上がったと感じたことはありませんか?

https://react-hook-form.com/

https://mui.com/

「registerを使ったフォームは動くのに、MUIのTextFieldSelectで急にエラーが出る」「Controllerって何?」
そんな疑問を持った方向けに、この記事ではRHFとMUIを組み合わせるときの基本と実装パターンを整理します。


RHFの基本:registerで値を取得する

まずはRHFの基本から。

RHFの基本的な使い方は、registerでフォーム項目を登録し、handleSubmitで送信処理を行うという流れです。
https://react-hook-form.com/get-started#Registerfields

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のTextFieldSelectを使おうとすると、途端に混乱が始まります。

<TextField label="名前" {...register("name")} />

これ自体は動く場合もありますが、SelectCheckboxになると一気に挙動がおかしくなります。
なぜでしょうか?

理由は、RHFとMUIでフォーム値の管理の仕組みが異なるからです。

  • RHFのregisterは「非制御コンポーネント(Uncontrolled)」として、DOMを直接参照して値を取得します。
  • 一方、MUIのTextFieldSelectは「制御コンポーネント(Controlled)」で、valueonChangeをpropsで受け取り、内部で状態を持ちません。

この「非制御 vs 制御」の違いが原因で、RHFのregisterだけではMUIコンポーネントを正しく制御できないのです。


Controllerで解決する

このギャップを埋めるのが、RHFのControllerです。

Controllerを使うと、RHFがvalueonChangeを制御し、MUIコンポーネントと自然に連携できるようになります。

https://react-hook-form.com/docs/usecontroller/controller

例: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.valuecheckedの扱いを合わせるのがポイントです。


Controllerを使うと何が良いのか?

項目 register Controller
コンポーネント inputなどのシンプル要素 MUIの複雑なUI
値の管理 DOMから取得 Reactのstate制御
柔軟性 シンプルだが制限あり 高い(制御・加工が容易)
バリデーション rulesでは不可 rulesで柔軟に設定可能

Controllerを使うことで、MUIコンポーネントの特性に合わせつつ、RHFの強み(再レンダーが少ない・バリデーションが簡単)を活かせます。

https://zod.dev/

https://react-hook-form.com/docs/useform#resolver


まとめ

  • RHFのregisterは、非制御コンポーネント向けの仕組み。
  • MUIのコンポーネントは制御コンポーネントなので、そのままでは動作しない。
  • Controllerを使えば、MUIとの組み合わせがスムーズに動作する。
  • 最初はテンプレートをコピーして覚えるのが近道。

慣れてくると、MUIとRHFの相性はとても良いと感じるはずです。
フォームの安定動作と保守性の高い実装を両立できるようになるので、ぜひこの組み合わせに慣れておきましょう。

Discussion