🤝

RHFとMUIを併用する時はreact-hook-form-muiを使ってみよう!

2024/12/11に公開

はじめに

個人的に、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を使用した場合

上記のようにControllerTextFieldを組み合わせて記述していた箇所が、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をコンポーネントに渡すことができるようになりました。
https://ja.react.dev/blog/2024/04/25/react-19#improvements-in-react-19

refの扱いがよりシンプルになったため、「もしかして、MUIも対応してくれているのでは?」と密かに期待していました。

そこで、試しにReact 19でMUIのSelectコンポーネントにそのままregisterを渡してみました。
結果はどうだったかというと…エラーが出てしまいました。

ま、ともかくRHFとMUIを併用するときはreact-hook-form-muiがオススメです!というお話でした。

参考

https://github.com/dohomi/react-hook-form-mui

Discussion