📋

MUI DataGridとdnd-kitでドラッグできるテーブルを作成する

2023/07/28に公開

はじめに

ここでは、私が個人開発をしていた時にちょっと苦労したDataGridをドラッグできるようにするテーブルを紹介します。

UIフレームワークであるMUIにある多機能テーブルDataGridと、ドラッグアンドドロップ機能を追加できるライブラリdnd-kitを使って、ドラッグで順番の入れ替えができるテーブルを作成します。

https://v5.mui.com/x/react-data-grid/

https://dndkit.com/

開発環境

開発環境は以下の通りです。

  • react: 18.2.0
  • @emotion/react: 11.10.6
  • @emotion/styled: 11.10.6
  • @mui/material: 5.11.15
  • @mui/x-data-grid: 6.0.3
  • @dnd-kit/core: 6.0.8
  • @dnd-kit/modifiers: 6.0.1
  • @dnd-kit/sortable: 7.0.2
  • @dnd-kit/utilities: 3.2.1

作成するもの

作成するテーブルはこちら。
サンプルをCodeSandboxに作成済みです。

テーブルの行をドラッグしてみてください。
入れ替えができるはずです。

コーディング

それでは作っていきましょう。

DataGridでテーブルを作成する

まずは基本となるテーブルをDataGridで作っていきます。
DataGridでは様々な機能を持ったテーブルが作成できるため、ご自身の目的に合ったテーブルを作成してください。

ここではDataGridそのものの解説は行わない為、DataGridについて知りたい方は、公式ドキュメントや他記事を参考にしてください。

公式ドキュメント
https://v5.mui.com/x/react-data-grid/

Zennの他記事
https://zenn.dev/longbridge/articles/1ea4c93702fdfc


テーブルを作成します。

import { DataGrid, GridColDef, GridValueGetterParams } from "@mui/x-data-grid";

const columns: GridColDef[] = [
  { field: "id", headerName: "ID", width: 90 },
  {
    field: "firstName",
    headerName: "First name",
    width: 150,
    editable: true
  },
  {
    field: "lastName",
    headerName: "Last name",
    width: 150,
    editable: true
  },
  {
    field: "age",
    headerName: "Age",
    type: "number",
    width: 110,
    editable: true
  },
  {
    field: "fullName",
    headerName: "Full name",
    description: "This column has a value getter and is not sortable.",
    sortable: false,
    width: 160,
    valueGetter: (params: GridValueGetterParams) =>
      `${params.row.firstName || ""} ${params.row.lastName || ""}`
  }
];
const initialRows = [
  { id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
  { id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
  { id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
  { id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
  { id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null },
  { id: 6, lastName: "Melisandre", firstName: null, age: 150 },
  { id: 7, lastName: "Clifford", firstName: "Ferrara", age: 44 },
  { id: 8, lastName: "Frances", firstName: "Rossini", age: 36 },
  { id: 9, lastName: "Roxie", firstName: "Harvey", age: 65 }
];

const SampleTable = () => {
  return (
    <DataGrid
      rows={rows}
      columns={columns}
      autoHeight
      checkboxSelection
      disableRowSelectionOnClick
    />
  )
}


ドラッグアンドドロップの適用範囲を設定する

dnd-kitの<DndContext>タグと<SortableContext>タグでテーブルを囲み、ドラッグアンドドロップの適用範囲を設定します。

ここではdnd-kitそのものの解説は行わない為、dnd-kitについて知りたい方は、公式ドキュメントや他記事を参考にしてください。

公式ドキュメント
https://dndkit.com/

Zennの他記事
https://zenn.dev/hamo/articles/725e4189bfc54d
https://zenn.dev/aldagram_tech/articles/c2cf248bd016fc

<DndContext>タグと<SortableContext>タグを追加します。
また、それぞれに必要なimportと処理も追加します。

  import { DataGrid, GridColDef, GridValueGetterParams } from "@mui/x-data-grid";
+ import {
+   DndContext,
+   closestCenter,
+   PointerSensor,
+   useSensor,
+   useSensors,
+   DragEndEvent,
+ } from '@dnd-kit/core';
+ import {
+   arrayMove,
+   SortableContext,
+   verticalListSortingStrategy,
+ } from '@dnd-kit/sortable';

  // データは省略

  const SampleTable = () => {
+   // ドラッグ可能なセンサーを設定
+   const sensors = useSensors(useSensor(PointerSensor));
+   
+   // ドラッグ終了時の処理
+   const handleDragEnd = (event: DragEndEvent) => {
+     const { active, over } = event;
+     if (over) {
+       const oldIndex = rows.findIndex((rows) => rows.id === active.id);
+       const newIndex = rows.findIndex((rows) => rows.id === over.id);
+       onDragChange(arrayMove(rows, oldIndex, newIndex));
+     }
+   };
    return (
+     <DndContext
+       sensors={sensors}
+       collisionDetection={closestCenter}
+       onDragEnd={handleDragEnd}
+       autoScroll={false}
+     >
+       <SortableContext
+         items={rows.map((row) => row.id)}
+         strategy={verticalListSortingStrategy}
+       >
          <DataGrid
            rows={rows}
            columns={columns}
            autoHeight
            checkboxSelection
            disableRowSelectionOnClick
          />
+       </SortableContext>
+     </DndContext>
    )
  }


ドラッグできる要素を設定する

どの要素をドラッグできるようにするか、dnd-kitのuseSortableを使って設定します。

テーブルのデータを入れ替えるためには、テーブルの行を入れ替え可能にする必要があるので、行に設定をしていきます。

DataGridでは、行コンポーネントである<GridRow>をオーバーライドすることで、実現できます。
<DataGrid>コンポーネントのcomponentsプロパティを使用することで、<DataGrid>内部のコンポーネントをオーバーライドすることができます。

  import {
    DataGrid,
    GridColDef,
    GridValueGetterParams,
+   GridRow,
+   GridRowProps
  } from "@mui/x-data-grid";
  import {
    DndContext,
    closestCenter,
    PointerSensor,
    useSensor,
    useSensors,
    DragEndEvent,
  } from '@dnd-kit/core';
  import {
    arrayMove,
    SortableContext,
    verticalListSortingStrategy,
+   useSortable,
  } from '@dnd-kit/sortable';
+ import { CSS } from '@dnd-kit/utilities';

  // データは省略
  
+ // ドラッグできる行コンポーネント
+ const DraggableGridRow = memo((params: GridRowProps) => {
+   const {
+     attributes,
+     listeners,
+     setNodeRef,
+     transform,
+     transition,
+     isDragging
+   } = useSortable({ id: params.rowId });
+   const style = {
+     transform: CSS.Transform.toString(transform),
+     transition
+   };
+ 
+   return (
+     <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
+       <GridRow {...params} />
+     </div>
+   );
+ });


  const SampleTable = () => {
    // ドラッグ可能なセンサーを設定
    const sensors = useSensors(useSensor(PointerSensor));

    // ドラッグ終了時の処理
    const handleDragEnd = (event: DragEndEvent) => {
      const { active, over } = event;
      if (over) {
        const oldIndex = rows.findIndex((rows) => rows.id === active.id);
        const newIndex = rows.findIndex((rows) => rows.id === over.id);
        onDragChange(arrayMove(rows, oldIndex, newIndex));
      }
    };
  
    return (
      <DndContext
        sensors={sensors}
        collisionDetection={closestCenter}
        onDragEnd={handleDragEnd}
        autoScroll={false}
      >
        <SortableContext
          items={rows.map((row) => row.id)}
          strategy={verticalListSortingStrategy}
        >
          <DataGrid
            rows={rows}
            columns={columns}
+           components={{ Row: DraggableGridRow }}
            autoHeight
            checkboxSelection
            disableRowSelectionOnClick
          />
        </SortableContext>
      </DndContext>
    )
  }


これにて完成です。
最初にお見せしたサンプルは、コンポーネントを分割しているので全く同じコードではありませんが、機能では同じものになります。


オーバーライドするための行を独自に作成したい場合は、Githubの<DataGrid>コンポーネントのコードを読みつつやってみましょう。

https://github.com/mui/mui-x/blob/51b77fb54e65b583a3e3c3c5f652485ce1c65690/packages/grid/x-data-grid/src/components/GridRow.tsx#L103

Discussion