0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MUI DataGridで日本語入力がバグる?IME問題の原因と対策まとめ

Posted at

MUI DataGridで日本語入力がバグる?IME問題の原因と対策まとめ

MUIのDataGridコンポーネントで日本語を入力しようとすると、以下のような奇妙な挙動に遭遇したことはありませんか?

  • 「て」と入力したのに「tえ」「ttええ」になる
  • 文字が確定する前に変な文字列が追加される
  • 編集しようとしたら未確定文字が消えてしまう

本記事では、この IME(日本語入力)とDataGridの相性問題の原因と、実用的な4つの解決策について解説します。


問題の再現コード

<DataGrid
  columns={[
    {
      field: 'name',
      headerName: '名前',
      width: 200,
      renderCell: (params) => (
        <input
          type="text"
          value={params.row.name}
          onChange={(e) => {
            const newName = e.target.value;
            setRows((prev) =>
              prev.map((row) =>
                row.id === params.row.id ? { ...row, name: newName } : row
              )
            );
          }}
        />
      ),
    },
  ]}
  rows={rows}
/>

このように、renderCell<input> を直接配置し、その中で setState を呼ぶと、IMEでの日本語入力中に以下のような不具合が発生します:

  • 文字が勝手に確定される

  • 連打入力で重複した文字が入る

  • 入力中にセルが再描画され、未確定文字が消える


原因:MUI DataGridとIMEの仕組みの衝突

  • renderCell は「表示モード用のセル描画」
  • 編集モードに移行せずに <input> を使うと、DataGridが入力状態を管理できない
  • IMEの未確定入力(composition中)に再レンダリングが発生すると、不正確に確定される

解決策①:renderEditCell を使う

MUIの推奨方法は、編集モード時にだけ <input> を描画する方法です。

function CustomEditCellComponent(props: GridRenderEditCellParams) {
  const [value, setValue] = useState(props.value);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newValue = e.target.value;
    setValue(newValue);
    props.api.setEditCellValue({
      id: props.id,
      field: props.field,
      value: newValue,
    });
  };

  return <input value={value} onChange={handleChange} autoFocus />;
}
<DataGrid
  columns={[
    {
      field: 'name',
      headerName: '名前',
      editable: true,
      width: 200,
      renderEditCell: CustomEditCellComponent,
    },
  ]}
  rows={rows}
/>

特徴

  • IME未確定文字の扱いが安定する
  • autoFocus も効く
  • 編集状態が明確に制御される

解決策②:processRowUpdate を使って値を保存

編集確定(Enter/Tab/Blur)時に、processRowUpdate を使って状態を一括で更新します。

<DataGrid
  editMode="cell"
  processRowUpdate={(newRow) => {
    setRows((prev) =>
      prev.map((row) => (row.id === newRow.id ? newRow : row))
    );
    return newRow;
  }}
  columns={[ /* 省略 */ ]}
  rows={rows}
/>

特徴

  • 編集ロジックと表示ロジックを分離できる
  • renderEditCellと併用することでIME安全な入力が可能
  • バリデーションやAPI連携もこの中でできる

解決策③:compositionイベントを使った応急処置

どうしてもrenderCellで常時 <input> を表示したい場合、compositionstart / compositionend を使ってIME入力中の更新を制御する方法があります。

function ComposingInput({ value }: { value: string }) {
  const [localValue, setLocalValue] = useState(value);
  const [isComposing, setIsComposing] = useState(false);

  // 親のsetRows関数が必要なのでpropsに渡す想定
  const updateRows = (newValue: string) => {
    setRows((prev) =>
      prev.map((row) =>
        row.id === someId ? { ...row, name: newValue } : row
      )
    );
  };

  return (
    <input
      value={localValue}
      onCompositionStart={() => setIsComposing(true)}
      onCompositionEnd={(e) => {
        setIsComposing(false);
        updateRows(e.target.value); // 確定時にだけ更新
      }}
      onChange={(e) => {
        setLocalValue(e.target.value);
        if (!isComposing) {
          updateRows(e.target.value);
        }
      }}
    />
  );
}

解決策④:renderCell + カスタムコンポーネント(useStateで内部管理)

renderCell<input> を直接書くとIME不具合が出やすいですが、状態管理をローカルに切り離すことで一部の問題を回避できます。

function EditableCell({
  params,
  onChange,
}: {
  params: GridRenderCellParams;
  onChange: (value: string, id: number) => void;
}) {
  const [value, setValue] = useState(params.value);

  return (
    <input
      value={value}
      onChange={(e) => {
        setValue(e.target.value);
        onChange(e.target.value, params.row.id);
      }}
    />
  );
}
<DataGrid
  columns={[
    {
      field: 'name',
      headerName: '名前',
      width: 200,
      editable: true,
      renderCell: (params) => (
        <EditableCell
          params={params}
          onChange={(value, id) => {
            setRows((prev) =>
              prev.map((row) => (row.id === id ? { ...row, name: value } : row))
            );
          }}
        />
      ),
    },
  ]}
  rows={rows}
/>

特徴

  • 常に <input> が表示されている
  • useState によって IME 中の文字が破棄されにくい
  • composition 対応よりもシンプルに動くことがある

注意点

  • 編集モードではないため、TabキーやEnterキーでの保存・フォーカス移動は自前で実装が必要
  • 仮想スクロールやソートでコンポーネントが破棄・再生成されると状態が失われる可能性あり

常時編集可能 vs 編集モードの比較

パターン 常時 <input> 表示 IME対応 編集モード フォーカス制御 スクロール耐性
renderCell + input(直接)
renderCell + useState(カスタム) △〜◎
renderCell + composition
renderEditCell + editable ❌(クリックで表示)
renderEditCell + processRowUpdate ❌(クリックで表示)

補足:editMode="cell"editMode="row" の違い

モード 説明
cell セル単位で編集開始/確定できる(細かい制御向け)
row 行全体を一括で編集・確定(一括更新・バリデ向け)

ベストプラクティスまとめ

方法 IME対応 安定性 推奨度
renderEditCell ✅✅✅
processRowUpdate - ✅✅
renderCell + useState △〜◎ ✅(場面による)
renderCell + composition ❌(補足的対応)
renderCell + input(直接)

まとめ

  • renderCellで直接<input>を書くと、IMEとぶつかって不具合が出やすい
  • MUIが用意している編集モード(editable, renderEditCell, processRowUpdate)を活用すれば安定動作が可能
  • renderEditCellでもrenderCellでも、カスタムコンポーネント内でuseStateを使いローカル状態を持つことで、IMEの未確定入力を安定的に扱いやすくなる
  • どうしても常時テキストボックスを表示したい場合は、renderCell + useState + カスタムコンポーネントの手法も検討の余地あり
  • UXや要件に応じて、適切に選択する必要がある
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?