本文へスキップ
ウェブエンジニア問題集
第10章

MUIのフォーム部品 — TextField・Select・Autocompleteの使い方

約8分
この章の目次開く

フォームはMUIの出番が最も多い領域です。ラベルの浮き上がり、フォーカス時の枠線、エラー時の赤字といった挙動が最初から入っているため、自作するより圧倒的に早く形になります。

一方で、Reactのフォームとしての作法を外すと動かなくなります。この章では、MUI固有の書き方とReact共通の落とし穴を分けて押さえます。

学習者学習者

<TextField /> を置いたら見た目はできたけど、入力しても文字が出ないことがあって…。

典型的な詰まり方です。最初の節で解決します。


controlledとuncontrolled

Reactの入力欄には2つの持ち方があります。MUIも同じです。

// uncontrolled: 値はDOMが持つ。初期値だけ渡す
<TextField defaultValue="初期値" />
 
// controlled: 値はReactのstateが持つ
<TextField value={name} onChange={(e) => setName(e.target.value)} />
tsx

value を渡したら onChange も必ず渡す。これが冒頭の「入力しても文字が出ない」の答えです。value だけ渡すと、Reactが毎回同じ値で上書きし続けます。


TextField — 見た目の3種類

variant で枠線の形が変わります。既定は outlined です。

variant見た目
outlined(既定)枠線で囲む
filled背景を塗る
standard下線だけ
<TextField label="名前" variant="outlined" />
tsx

サイズは size="small" で詰められます。管理画面のように入力欄が多い画面では、小さいほうが扱いやすいことが多いです。

エラーとヘルプテキスト

<TextField
  label="メールアドレス"
  error={!!errors.email}
  helperText={errors.email ?? '会社のアドレスを入力してください'}
/>
tsx

error を true にすると、枠線・ラベル・ヘルプテキストがまとめて赤くなります。個別に色を指定する必要はありません。

フォームを設計するイメージ

Select — プルダウン

いちばん手軽なのは TextField に select を付ける方法です。ラベルやエラー表示の作法がそのまま使えます。

<TextField select label="部署" value={dept} onChange={(e) => setDept(e.target.value)}>
  <MenuItem value="sales">営業</MenuItem>
  <MenuItem value="dev">開発</MenuItem>
</TextField>
tsx

Select を単体で使うこともできますが、その場合はラベルの配置を自分で組む必要があります。

<FormControl fullWidth>
  <InputLabel id="dept-label">部署</InputLabel>
  <Select labelId="dept-label" label="部署" value={dept} onChange={handleChange}>
    <MenuItem value="sales">営業</MenuItem>
  </Select>
</FormControl>
tsx

Select 単体では InputLabel の id と labelId を対応させ、さらに Select にも同じ label を渡すのが要点です。label を渡さないと、枠線がラベルの文字の下を通ってしまいます。


Checkbox・Radio・Switchとラベル

これらは単体だと四角や丸だけで、文字が付きません。FormControlLabel で包みます。

<FormControlLabel
  control={<Checkbox checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />}
  label="利用規約に同意する"
/>
tsx

FormControlLabel はラベル文字のクリックでも反応するようにしてくれます。アクセシビリティの観点でも、自分で <label> を書くより確実です。

ラジオボタンは RadioGroup でまとめます。

<RadioGroup value={plan} onChange={(e) => setPlan(e.target.value)}>
  <FormControlLabel value="free" control={<Radio />} label="無料プラン" />
  <FormControlLabel value="pro" control={<Radio />} label="有料プラン" />
</RadioGroup>
tsx

Autocomplete — 検索できる選択欄

選択肢が多いときは Autocomplete を使います。入力で絞り込めます。

<Autocomplete
  options={users}
  getOptionLabel={(option) => option.name}
  value={selected}
  onChange={(event, newValue) => setSelected(newValue)}
  renderInput={(params) => <TextField {...params} label="担当者" />}
/>
tsx

他のコンポーネントと違う点が2つあります。

  • renderInput が必須。 入力欄そのものは自分で用意します。params をそのまま TextField に展開してください
  • onChange の第2引数が値。 テキスト入力の e.target.value とは形が違います

getOptionLabel を省略した場合は option.label が使われます。選択肢に label というキーがあれば書かなくても動きます。

「値が選択されない」の原因

学習者学習者

value にちゃんとオブジェクトを入れてるのに選択状態にならないし、コンソールに警告が出る…。

Autocomplete は、value と選択肢を「参照が同じかどうか」で比較します。型定義にも「選択されるには参照等価である必要がある」と明記されています。

APIから取り直したオブジェクトは、中身が同じでも別の参照です。そのため一致せず、選択状態になりません。

解決策は、比較の仕方を教えることです。

<Autocomplete
  options={users}
  value={selected}
  isOptionEqualToValue={(option, value) => option.id === value.id}
  // ...
/>
tsx

IDで比べるようにすれば、参照が違っても一致します。Autocomplete を使うときはほぼ必須の設定だと考えてください。


よくあるハマりどころ

入力しても文字が変わらない

value を渡して onChange を渡していません。

「uncontrolledからcontrolledに変わった」と警告が出る

初期値が undefined です。空文字にします。

チェックボックスが反応しない

e.target.value を読んでいます。e.target.checked です。

Selectの枠線がラベルに重なる

Select に label を渡していません。InputLabel だけでなく Select 側にも必要です。

フォーム全体の状態管理が煩雑になる

入力欄が10個を超えたあたりから、useState を並べる方式は破綻します。React Hook Formのようなフォームライブラリと組み合わせるのが一般的です。MUIのコンポーネントは ref と onChange を受け取れるので連携できます。


ちゃんと使うためのポイント

  • value を渡すなら onChange も渡す。初期値は undefined ではなく空文字
  • TextField の既定variantは outlined
  • error と helperText はセット。helperText は常に何か入れて高さを固定する
  • チェックボックスとスイッチは e.target.checked
  • Autocomplete は renderInput が必須で、onChange の第2引数が値
  • Autocomplete は参照で比較する。isOptionEqualToValue をほぼ必ず指定する

次の章では、Dialog・Snackbar・Menuといった、画面の上に重なるコンポーネントを扱います。


参考リンク