MUIのコンポーネントをReact Hook Formにつなぐと、値の反映とエラーメッセージは動くのに、フォーカス移動だけが反応しないことがあります。原因はMUIが入力要素を外側の要素で包んでいる構造にあり、Controllerから渡すrefをどの要素に着地させるかで挙動が分かれます。Material UI v9ではCheckbox・Radio・SwitchのinputRefが廃止されたため、v7時代の書き方のままだとこの問題が起きます。
この記事は、Material UI 9.5.0・React Hook Form 7.91.0・MUI X Date Pickers 9.17.0を実際にインストールした環境で書いています。正常系の実装例はjsdom上で実行し、React警告が出ないことと、値の送信・バリデーション・フォーカスの各挙動を確認しました。不具合を示す比較例では、本文に示す警告やフォーカス失敗も確認しています。React Hook Form 8.0.0-beta.5でも同じ検証を回し、v8で書き換えが必要になる箇所を拾っています。
まとめ
インストールはnpm install @mui/material @emotion/react @emotion/styledとnpm install react-hook-formの2本が基本です。スキーマ検証を足すなら@hookform/resolversは6.0.1以降を選びます(6.0.0は素のnpmでERESOLVEになる不具合がありました)。
MUIコンポーネントはControllerでラップし、renderの引数を({ field: { ref, ...field }, fieldState })と分解して、refを入力要素にだけ渡します。渡し先はTextFieldとDatePickerならinputRef、Checkbox・Radio・Switchならv9でslotProps={{ input: { ref } }}に変わりました。{...field}をそのまま展開するとrefが外側の要素に着地し、setFocusと送信時の自動フォーカスが警告なしに効かなくなります。
React Hook Form v8はまだbetaで、npm i react-hook-form@betaで入るのは2025年12月の8.0.0-beta.0です。v8ではuseFieldArrayのfieldsの識別子がidからkeyに変わるため、key={item.id}のままだとReactのkey警告が出ます。@hookform/resolversとreact-hook-form-muiはv8 betaをpeerの範囲に含めておらず、インストールの時点で止まります。
MUI v9とReact Hook Formのインストール手順
インストールコマンドと検証したバージョン
MUI公式のInstallationページが案内する既定のコマンドは次の1本です。@emotion/reactと@emotion/styledは既定のスタイリングエンジンで、styled-components版へ差し替える構成を選ばない限り一緒に入れます。reactとreact-domはpeer dependenciesなので、先に入っている必要があります。
npm install @mui/material @emotion/react @emotion/styled
React Hook Formと日付入力、スキーマ検証は別コマンドで追加します。
npm install react-hook-form
npm install @mui/x-date-pickers date-fns
npm install @hookform/resolvers zod
検証環境に入ったバージョンは次のとおりです(2026年11月3日時点のlatest)。
| パッケージ | バージョン | 公開日 |
|---|---|---|
| @mui/material | 9.5.0 | 2026-10-09 |
| react-hook-form | 7.91.0 | 2026-10-23 |
| @mui/x-date-pickers | 9.17.0 | 2026-10-30 |
| @hookform/resolvers | 7.0.1 | 2026-11-01 |
| zod | 4.6.7 | 2026-10-12 |
| react / react-dom | 19.3.1 | 2026-10-14 |
| date-fns | 4.4.0 | 2026-05-29 |
MUI本体のテーマ設定やGridの使い方はMaterial UIとは:v9系の導入からGrid・テーマ設定までコード付きで解説にまとめています。この記事ではフォームの接続に絞ります。
@hookform/resolversのバージョン選び
@hookform/resolversは2026年10月だけでメジャーが2回上がりました。6.0.0(10月19日)は、任意のpeerに入っていた@typeschema/main経由でeffectの版が衝突し、npm install react-hook-form zod @hookform/resolversがERESOLVEで止まる状態でした。6.0.1(10月27日)で@typeschema/mainがpeerから外れ、この衝突は解消しています。
7.0.0(10月31日)の破壊的変更はTypeBoxリゾルバだけで、peerが@sinclair/typeboxからtypebox 1.x へ移りました。Zodを使うフォームは7系へそのまま上げられます。6.0.0で固定しているlockfileがあれば、6.0.1以降に上げておくのが先です。
Material UIのv8欠番とv10 alpha
npmの@mui/materialには8系が1件も公開されていません。v7.0.0(2025年3月26日)の次がv9.0.0(2026年4月8日)で、MUIの発表記事は「Material UI moves from v7 straight to v9 (there is no Material UI v8, like there is no v2)」と書いています。先行していたMUI X v9とメジャー番号を揃えるための欠番です。MUI X側にはv8が実在するので(@mui/x-date-pickersのlatest-v8は8.29.5)、「MUI v8」で見つかる情報の多くはData GridやDate Pickersの話です。
次のメジャーは、10.0.0-alpha.0が2026年10月12日にnextタグで公開されています。破壊的変更として告知されたのはAutocompleteのポップアップの影(elevation)を1から8へ変える1件だけで、v10の移行ガイドは11月3日時点で導入文しかありません。本番のフォームはv9で組み、v10はalphaが外れてから評価するのが妥当です。
React 18以下で必要なreact-isの固定
@mui/material 9.5.0は依存にreact-is@^19.3.0を持ちます。React 18以下のプロジェクトではreact-isの版が食い違うため、公式Installationページは使用中のReactと同じ版へ固定するよう案内しています。
npm install [email protected]
{
"overrides": {
"react-is": "^18.3.1"
}
}
React 19系ではこの手当ては不要です。v9へ上げた直後に原因の分からないprop型エラーが出たら、まずここを確認してください。
ControllerでMUIのTextFieldをつなぐ基本形
Controllerを使う最小構成
MUIのTextFieldはFormControlとInputBaseを重ねた構造なので、値の出入りを明示的に受け渡すControllerで扱うのが基本です。useFormやwatchなどReact Hook Form自体の基本APIはReact Hook Formとは?使い方とv7の書き方・検証タイミングで整理しています。
import { useForm, Controller, useFieldArray } from 'react-hook-form';
import {
TextField, Button, MenuItem, RadioGroup, Radio, Checkbox,
FormControlLabel, FormControl, FormHelperText, FormLabel, FormGroup,
} from '@mui/material';
export function ProfileForm({ onValid }) {
const { control, handleSubmit } = useForm({
defaultValues: { email: '' },
});
return (
<form onSubmit={handleSubmit(onValid)}>
<Controller
name="email"
control={control}
rules={{
required: 'メールアドレスを入力してください',
pattern: { value: /^[^@\s]+@[^@\s]+$/, message: 'メールアドレスの形式が正しくありません' },
}}
render={({ field: { ref, ...field }, fieldState }) => (
<TextField
{...field}
inputRef={ref}
label="メールアドレス"
error={Boolean(fieldState.error)}
helperText={fieldState.error?.message ?? ''}
/>
)}
/>
<Button type="submit" variant="contained">送信</Button>
</form>
);
}
空のまま送信すると「メールアドレスを入力してください」、fooではpatternのメッセージが出て、どちらも送信ハンドラは呼ばれません。[email protected]を入れると{ email: "[email protected]" }が送信ハンドラに渡りました。
React Hook Formの公式ドキュメントの例はonChangeなどを1つずつ渡してinputRef={field.ref}を別に書く形で、上の書き方はそれを短くしたものです。
registerを直接渡す場合との使い分け
<TextField {...register('nick', { required: '必須です' })} />もMUIで動きます。値の反映({ nick: "taro" }が送信ハンドラへ到達)、必須チェック、setFocusのすべてが通りました。
ただしSelect・DatePickerなどの制御入力はControllerで接続すると値とイベントを明示的に扱えるため、MUIのフォームはControllerに統一したほうが読みやすくなります。
setFocusとエラー時の自動フォーカスが効かなくなる原因
fieldをそのまま展開したときの実測結果
解説記事でよく見かける次の書き方には落とし穴があります。
// フォーカスが効かなくなる書き方
render={({ field, fieldState }) => (
<TextField
{...field}
inputRef={field.ref}
error={Boolean(fieldState.error)}
helperText={fieldState.error?.message ?? ''}
/>
)}
fieldにはrefが含まれるため、{...field}の展開でTextFieldのref propにも同じコールバックが渡ります。TextFieldはrefをルートのFormControl(div)へ、inputRefを内部のinputへ渡すので、React Hook Formが覚えるフォーカス先がdivで上書きされます。
4つの書き方を同じ条件で実行し、setFocusの呼び出しと、送信時にReact Hook Formが行う自動フォーカスを測りました。7.91.0と8.0.0-beta.5で結果は同じです。
| 書き方 | setFocus | 送信時の自動フォーカス |
|---|---|---|
| {…field} と inputRef={field.ref} | 効かない | 効かない |
| {…field} のみ | 効かない | 効かない |
| ref を分解して inputRef={ref} | 効く | 効く |
| {…register(‘nick’)} | 効く | 効く |
値の反映とエラーメッセージは4つとも正常です。フォーカスだけが警告なしに止まるので、「エラーの項目まで自動でスクロールしない」という形で後から気付きます。
refを分解する修正
// 抜粋(importは前掲のとおり)
const { control, handleSubmit, setFocus } = useForm({
defaultValues: { email: '' },
});
const focusFirstError = (errors) => setFocus(Object.keys(errors)[0]);
return (
<form onSubmit={handleSubmit(onValid, focusFirstError)}>
<Controller
name="email"
control={control}
rules={{ required: '必須項目です' }}
render={({ field: { ref, ...field }, fieldState }) => (
<TextField
{...field}
inputRef={ref}
error={Boolean(fieldState.error)}
helperText={fieldState.error?.message ?? ''}
/>
)}
/>
<Button type="submit">送信</Button>
</form>
);
handleSubmitの第2引数はバリデーションNG時に呼ばれます。もっともshouldFocusErrorは既定でtrueなので、refが正しければsetFocusを書かなくても最初のエラー項目へフォーカスが移ります(表の「送信時の自動フォーカス」)。
テストでsetFocusを確かめるときは、呼んだ直後にdocument.activeElementを見ても入力要素になっていません。setFocusはsetErrorとの競合を直した修正(#13169)以降、setTimeout越しにフォーカスするためです。今回の検証でも、呼び出し直後はすべての書き方でfalse、次のタイマー後に判定して上の表の結果になりました。
registerでは動くのにControllerで動かない理由
React Hook Formの配布ソースを読むと、2つのrefコールバックは別物です。register側は、受け取った要素にvalueが無ければquerySelectorAll('input,select,textarea')で配下の先頭の入力要素を取り出して登録します。TextFieldのルートがdivでも内部のinputまで届くのはこのためです。
Controller側は受け取った要素を掘り下げません。7.91.0では要素を{ focus, select, setCustomValidity, reportValidity }のラッパーに包んで保存し、focus()は渡された要素のfocus()をそのまま呼びます。divにfocus()を呼んでも何も起きません。
{...field}を展開する実装では、入力要素だけにrefを渡す処理を共通化できるため、TextField用のラッパーを1つ作り、フォーム側ではrefを意識しない形にしておくのが確実です。
Checkbox・Radio・Switchはv9でslotProps.input.refへ移行
Material UI v9では、Checkbox・Radio・SwitchのinputRefとinputPropsが削除されました。v9の移行ガイドは「inputRef — use slotProps.input.ref instead」と3部品それぞれに書いています。TextFieldとDatePickerのinputRefは残っているので、部品ごとに渡し先が違う点に注意が要ります。
9.5.0でrequiredの項目を空のまま送信し、送信時の自動フォーカスが入力要素に移るかを測りました。
| 部品と書き方 | 自動フォーカス | React警告 |
|---|---|---|
| Checkbox {…field} | 効かない | なし |
| Checkbox inputRef={ref} | 効かない | あり |
| Checkbox slotProps.input.ref | 効く | なし |
| RadioGroup {…field} | 効かない | なし |
| 先頭のRadioに slotProps.input.ref | 効く | なし |
| DatePicker inputRef={ref} | 効く | なし |
| DatePicker {…field} | 効かない | なし |
CheckboxにinputRefを渡すと、v9ではpropとして解釈されずにそのままDOMへ流れ、「React does not recognize the inputRef prop on a DOM element」という警告が出ます。警告が出るだけましで、{...field}で渡した場合はrefがルートのspanに着地して何も知らせずに止まります。v7からv9へ上げたフォームは、Checkbox・Radio・Switchの行をinputRefで検索してslotPropsへ書き換えてください。
RadioGroupはグループ自体がdivなので、refは先頭のRadioのslotProps.inputへ渡します。Autocompleteをつなぐ場合のslotPropsへの移行はMUI Autocompleteの実装と落とし穴|isOptionEqualToValueとv9のslotProps移行で扱っています。
Select・ラジオ・チェックボックス・日付の接続パターン
TextFieldのselectによるプルダウン
// 抜粋(importとuseFormの宣言は前掲のとおり)
<Controller
name="plan"
control={control}
rules={{ required: 'プランを選択してください' }}
render={({ field: { ref, ...field }, fieldState }) => (
<TextField
{...field}
inputRef={ref}
select
label="プラン"
error={Boolean(fieldState.error)}
helperText={fieldState.error?.message ?? ''}
>
<MenuItem value="basic">Basic</MenuItem>
<MenuItem value="pro">Pro</MenuItem>
</TextField>
)}
/>
TextFieldにselectを付けると、Select・InputLabel・FormHelperTextをまとめて描画するので記述が短くなります。defaultValuesに空文字を置けば未選択から始まります。操作対象は<div role="combobox">で、テストから開くときはclickではなくmousedownを発火させます(SelectInputがonMouseDownで開くため)。
RadioGroupと単体Checkboxの値の受け渡し
// 抜粋(importとuseFormの宣言は前掲のとおり)
<Controller
name="size"
control={control}
render={({ field: { ref, ...field } }) => (
<RadioGroup {...field} row aria-label="サイズ">
<FormControlLabel value="s" control={<Radio slotProps={{ input: { ref } }} />} label="S" />
<FormControlLabel value="m" control={<Radio />} label="M" />
</RadioGroup>
)}
/>
<Controller
name="agree"
control={control}
rules={{ required: '利用規約への同意が必要です' }}
render={({ field: { value, ref, ...field }, fieldState }) => (
<FormControl error={Boolean(fieldState.error)}>
<FormControlLabel
control={<Checkbox {...field} checked={value} slotProps={{ input: { ref } }} />}
label="利用規約に同意する"
/>
<FormHelperText>{fieldState.error?.message ?? ''}</FormHelperText>
</FormControl>
)}
/>
RadioGroupはvalueとonChangeをそのまま受け取ります。Checkboxはvalueではなくcheckedで状態を持つので、fieldからvalueを抜いてcheckedへ渡し替えます。defaultValuesにsize: 'm'とagree: falseを置くと、Mが初期選択・未チェックの状態で始まり、送信値は{"plan":"pro","size":"m","agree":true}になりました。
複数選択のチェックボックス群
「使用言語を複数選ぶ」のように値を配列で持つ場合は、1つのControllerの中でCheckboxを並べ、onChangeで配列を作り直して渡します。
// 抜粋(defaultValues: { langs: [] })
const LANGS = ['TypeScript', 'Go', 'Python'];
<Controller
name="langs"
control={control}
rules={{ validate: (v) => v.length > 0 || '1つ以上選択してください' }}
render={({ field: { value, onChange, onBlur, ref }, fieldState }) => (
<FormControl component="fieldset" error={Boolean(fieldState.error)}>
<FormLabel component="legend">使用言語</FormLabel>
<FormGroup>
{LANGS.map((lang, i) => (
<FormControlLabel
key={lang}
label={lang}
control={
<Checkbox
checked={value.includes(lang)}
onBlur={onBlur}
slotProps={{ input: { ref: i === 0 ? ref : undefined } }}
onChange={(e) =>
onChange(e.target.checked ? [...value, lang] : value.filter((v) => v !== lang))
}
/>
}
/>
))}
</FormGroup>
<FormHelperText>{fieldState.error?.message ?? ''}</FormHelperText>
</FormControl>
)}
/>
配列の必須チェックはrequiredではなくvalidateで長さを見ます。未選択で送信するとメッセージが出て先頭のチェックボックスにフォーカスが移り、TypeScriptを選択→Pythonを選択→TypeScriptを解除→TypeScriptを再選択した結果は{"langs":["Python","TypeScript"]}でした。配列は操作した順に並ぶので、表示順で送りたいなら送信前にLANGSの順へ並べ直します。
FormControlがerrorとrequiredを子へ配る仕組み
MUIにはform要素そのものを包むFormコンポーネントはありません(9.5.0のexportにあるのはFormControl・FormControlLabel・FormGroup・FormHelperText・FormLabel)。FormControlは1項目分の入れ物で、自分に付けたerrorやrequiredをContext経由で子のFormLabel・FormHelperTextへ配ります。
<FormControl error required>の中にFormLabelとFormHelperTextを置くと、両方にMui-errorクラスが付き、ラベルには必須の「*」が付きました。上のチェックボックスの例でFormHelperTextにerrorを個別に書かなくてよいのはこの仕組みのためです。自作の部品で同じ状態を読みたいときはuseFormControl()を使います。
DatePickerとLocalizationProviderの組み合わせ
// 抜粋(controlは前掲のuseFormから受け取る)
import { LocalizationProvider, DatePicker } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { ja } from 'date-fns/locale';
<LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={ja}>
<Controller
name="due"
control={control}
defaultValue={null}
rules={{
required: '期日を入力してください',
validate: (value) =>
(value instanceof Date && !Number.isNaN(value.getTime())) || '有効な日付を入力してください',
}}
render={({ field: { ref, ...field }, fieldState }) => (
<DatePicker
{...field}
inputRef={ref}
label="期日"
format="yyyy/MM/dd"
slotProps={{
textField: {
error: Boolean(fieldState.error),
helperText: fieldState.error?.message ?? '',
},
}}
/>
)}
/>
</LocalizationProvider>
requiredだけではInvalid Dateが送信ハンドラまで通るため、validateで日付として有効かも確かめます。setValue('due', new Date('invalid'))の状態で送信すると「有効な日付を入力してください」で止まりました。エラー表示はslotProps.textFieldから内側のTextFieldへ渡します。フォームが持つ値はDateオブジェクトのままで、2026/09/01を入れると2026年9月1日のDateが送信ハンドラに渡りました。APIへ送る直前に文字列化しておくと、画面の表示形式と送信形式を分けて管理できます。
useFieldArrayによる可変長フォームの実装
// 抜粋(importとuseFormの宣言は前掲のとおり)
const { control, handleSubmit } = useForm({
defaultValues: { tasks: [{ title: '' }] },
});
const { fields, append, remove } = useFieldArray({ control, name: 'tasks' });
return (
<form onSubmit={handleSubmit(onValid)}>
{fields.map((item, index) => (
<Controller
key={item.id}
name={`tasks.${index}.title`}
control={control}
rules={{ required: 'タスク名を入力してください' }}
render={({ field: { ref, ...field }, fieldState }) => (
<TextField
{...field}
inputRef={ref}
label={`タスク${index + 1}`}
error={Boolean(fieldState.error)}
helperText={fieldState.error?.message ?? ''}
/>
)}
/>
))}
<Button type="button" onClick={() => append({ title: '' })}>行を追加</Button>
<Button type="button" onClick={() => remove(fields.length - 1)}>末尾を削除</Button>
<Button type="submit">送信</Button>
</form>
);
keyには配列のindexではなく、useFieldArrayが行ごとに振るitem.idを渡します。nameはテンプレートリテラルでtasks.${index}.titleと組み立て、Controllerは行の中の項目ごとに置きます。初期1行から2回追加して3行、末尾削除で2行になり、各行に独立して必須チェックが効き、送信値は{"tasks":[{"title":"設計"},{"title":"実装"}]}でした。
React Hook Form v8 betaでMUI連携に効く変更
fieldsの識別子変更(idからkey)
8.0.0-beta.1のリリースノートは、useFieldArrayのidをkeyへ改名し、keyNameオプションを削除すると告知しています。8.0.0-beta.5でfields[0]のプロパティを出すと['title', 'key']で、7.91.0の['title', 'id']から変わっていました。
前章のコードをv8 betaでそのまま動かすと、item.idがundefinedになり、Reactの「Each child in a list should have a unique key prop」警告が出ます。Controller経由の制御入力なので、先頭行を消しても値のずれは起きませんでしたが、key={item.key}へ書き換えるまでは警告が消えません。appendに渡すデータにidという項目があっても、v8では描画用のkeyとは別物として送信値に残ります。
beta.1の告知には「setValueではuseFieldArrayを直接更新しない、replaceを使う」ともあります。ただしbeta.5でsetValue('tasks', [{ title: 'A' }])を実行すると行は1行に更新され、7.91.0と同じ挙動でした。betaの間に挙動が動いているので、配列ごと入れ替える処理は両方で同じ結果になるreplaceで書いておくのが安全です。
Controllerのref保存形式の変更
v8ではControllerのrefが受け取った要素をそのまま保存します(beta.1の「pass input ref instead of partial」)。7.91.0のように4つのメソッドだけを持つラッパーではなくなりました。それでも配下を掘り下げない点は同じで、{...field}を展開するとフォーカス先がTextFieldのdivになり、setFocusも自動フォーカスも効きません。refを分解して入力要素へ渡す修正は、v8でもそのまま必要です。
v8 betaで止まるresolversとreact-hook-form-muiのインストール
npmのbetaタグは2025年12月7日の8.0.0-beta.0を指したまま更新されていません。最新のbetaを試すにはnpm install [email protected]と版を明示します。
この状態で@hookform/[email protected]を足すと、peerのreact-hook-form@"^7.55.0"を満たせずERESOLVEになります。[email protected]もpeerが>=7.62.0で、npmの範囲指定はプレリリースを含まないため同じく止まります。--legacy-peer-depsを付ければインストールは通りますが、peerの範囲外なので動作の保証はありません。v8は試験用のブランチで評価し、本番は7.91系に置くのが現実的です。
rulesとzodResolverの使い分け
Controllerのrulesは項目単位のバリデーションです。パスワードの再入力一致や開始日と終了日の前後関係のように項目をまたぐ条件、サーバー側と同じ検証を共有したい場合はスキーマに寄せます。
// 抜粋(useFormのimportは前掲のとおり)
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
const schema = z.object({
email: z.email({ message: 'メールアドレスの形式が正しくありません' }),
age: z.coerce.number().int().min(18, { message: '18歳以上で入力してください' }),
});
const { control, handleSubmit } = useForm({
defaultValues: { email: '', age: '' },
resolver: zodResolver(schema),
});
@hookform/resolvers 7.0.1にzod 4.6.7のスキーマを渡す形です。ageに17なら「18歳以上で入力してください」で送信が止まり、20なら数値型の20が送信ハンドラに渡りました。z.coerce.number()がTextFieldから来る文字列を送信時に数値へ変換するので、フォームの値は文字列のまま持てます。
項目が数個で条件が独立しているならrulesで足ります。スキーマを別ファイルへ切り出す価値が出るのは、同じ検証をAPI側やテストでも使い回すときです。Zodの書き方はReactのZodフォームバリデーション実装|zodResolverの導入とrefine・Zod 4の落とし穴、React Hook Form以外の選択肢はTanStack Formとは?使い方とReact Hook Formとの違いで比べています。
react-hook-form-muiを採用すべき場面
react-hook-form-muiはControllerでラップ済みのMUI部品を配る第三者パッケージです。最新は9.0.1(2026年4月22日)で、TextFieldElement・SelectElement・CheckboxElement・RadioButtonGroup・AutocompleteElementなどを提供し、DatePicker系はreact-hook-form-mui/date-pickersに分かれています。必須のpeerは@mui/material ^9.0.0・@mui/system ^9.0.0・react-hook-form >=7.62.0・react >=18 <21です。
向くのは、入力項目の多い管理画面やアンケート画面で、見た目をパッケージの流儀に合わせてよい場合です。前章までのrefの分解やslotPropsへの渡し替えを書かずに済みます。設問が数十項目に及ぶ入力画面を業務システムとして作る場合は、アンケート管理システム開発のように画面設計から任せる選択肢もあります。
向かないのは、デザインシステムを自前で持ち、slotPropsやテーマで細かく上書きしている場合です。ラッパー越しの上書きは調べにくく、前章のとおりRHF v8 betaもpeerの範囲外です。自前のControllerはフィールドあたり十数行なので、まず共通ラッパーで足りるかを確かめてください。
よくある質問
MUIのインストールコマンドは何ですか?
既定構成ではnpm install @mui/material @emotion/react @emotion/styledで、reactとreact-domは先に入れておきます。アイコンは@mui/icons-material、日付入力は@mui/x-date-pickersと日付ライブラリを追加します。
Material UI v8はどこで配布されていますか?
Material UI本体にv8はありません。v7.0.0の次がv9.0.0で、MUI Xとメジャー番号を揃えるための欠番です。「MUI v8」の情報はほとんどがMUI X側のv8を指しています。
React Hook Form v8はもう使えますか?
2026年11月3日時点の正式版は7.91.0で、v8は8.0.0-beta.5(10月23日)までのbetaです。betaタグは古いbeta.0を指すので、試すなら版を明示して入れます。MUIと組み合わせる場合は、fieldsのkeyへの改名と、@hookform/resolvers・react-hook-form-muiがpeerでインストールできない点が最初の障害になります。
MUIのSelectBoxをReact Hook Formで扱うにはどうしますか?
TextFieldにselectを付け、ControllerでラップしてrefをinputRefに、残りを{...field}で渡すのが最短です。エラー表示はerrorとhelperTextで完結します。Selectを単体で使うなら、FormControlの中にInputLabelとFormHelperTextを自分で並べます。
setFocusを呼んでもフォーカスが移らないのはなぜですか?
多くは{...field}をそのまま展開し、refがTextFieldのルートのdivに着地しているのが原因です。({ field: { ref, ...field } })と分解し、TextFieldならinputRef、Checkbox・Radio・SwitchならslotProps={{ input: { ref } }}に渡します。テストではsetFocusがsetTimeout越しに動く点も考慮します。