[React]ドラッグ&ドロップできるテーブルをMUIとreact-beautiful-dndで作成してみました

Material-UI
スポンサーリンク

Reactで開発をしていて、テーブルの行を自由に並び替えたいって思うことありますよね。

ということで、ドラッグ&ドロップで行を入れ替えられるテーブルを作成してみました。

See the Pen react-dnd-table by amateur-engineer (@amaeng) on CodePen.

1から実装するのは面倒だったので、

  • テーブルはMaterial-UI
  • ドラッグ&ドロップはreact-beautiful-dnd

を使って実装しています。

それでは、作り方を紹介していきます。

ドラッグ&ドロップできるテーブルの作り方

事前準備

まずは下記の環境を整えます。

  • React + TypeScript
  • Material-UI(UIライブラリ)
  • react-beautiful-dnd(ドラッグ&ドロップ部分)

React + TypescriptとMaterial-UIについては別記事に書きましたので、そちらを見てください。


react-beautiful-dndをTypeScriptで使用するには、npm installでreact-beautiful-dndと@types/react-beautiful-dndをインストールします。

npm install react-beautiful-dnd
npm install @types/react-beautiful-dnd

@types/から始まるパッケージは、JavaScriptのライブラリに型定義を付与するファイルが入っているらしいです。

通常のテーブルを作成

import * as React from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

const rows = [
  {name: 'item1', index: 0, order: 0},
  {name: 'item2', index: 1, order: 1},
  {name: 'item3', index: 2, order: 2},
  {name: 'item4', index: 3, order: 3},
  {name: 'item5', index: 4, order: 4},
];

export default function BasicTable() {
  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Index</TableCell>
            <TableCell>Order</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map((row) => (
            <TableRow
              key={row.name}
            >
              <TableCell >{row.name}</TableCell>
              <TableCell>{row.index}</TableCell>
              <TableCell>{row.order}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

最初に基盤となるテーブルを作成します。
Material-UIのTableを使用して、できるだけ簡素に作りました。

この後並び替えで使用するorderとindexは0オリジンです。

ドラッグ&ドロップできるように修正

import * as React from "react";
import {
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  Paper,
} from "@mui/material";
import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";

export default function DragAndDropTable() {
  const [rows, setRows] = React.useState([
    { name: "item1", index: 0, order: 0 },
    { name: "item2", index: 1, order: 1 },
    { name: "item3", index: 2, order: 2 },
    { name: "item4", index: 3, order: 3 },
    { name: "item5", index: 4, order: 4 },
  ]);

  const reorder = (startIndex: number, endIndex: number) => {
    const result = Array.from(rows);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);
    result.map((row, index) => (row.order = index));
    return result;
  };

  const onDragEnd = (result: DropResult) => {
    const { source, destination } = result;
    if (!destination) {
      return;
    }
    const update = reorder(source.index, destination.index);
    setRows(update);
  };

  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Index</TableCell>
            <TableCell>Order</TableCell>
          </TableRow>
        </TableHead>
        <DragDropContext onDragEnd={onDragEnd}>
          <Droppable droppableId={"dndTableBody"}>
            {(provided) => (
              <TableBody ref={provided.innerRef} {...provided.droppableProps}>
                {rows.map((row, index) => (
                  <Draggable
                    draggableId={row.name}
                    index={index}
                    key={row.name}
                  >
                    {(provided) => (
                      <TableRow
                        ref={provided.innerRef}
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                      >
                        <TableCell style={{minWidth: `${Math.floor(100/3)}vw`}}>{row.name}</TableCell>
                        <TableCell style={{minWidth: `${Math.floor(100/3)}vw`}}>{row.index}</TableCell>
                        <TableCell style={{minWidth: `${Math.floor(100/3)}vw`}}>{row.order}</TableCell> 
                      </TableRow>
                    )}
                  </Draggable>
                ))}
                {provided.placeholder}
              </TableBody>
            )}
          </Droppable>
        </DragDropContext>
      </Table>
    </TableContainer>
  );
}

役割ごとに説明していきます。

全体はDraoDropContextで囲む
<DragDropContext onDragEnd={onDragEnd}>
 {/** ドラッグ&ドロップしたいエリア  */}
</DragDropContext>

ドラッグ&ドロップしたいエリア全体をDragdropContextで囲みます。

ドロップエリアはDroppableで囲む
<Droppable droppableId={"dndTableBody"}>
  {(provided) => (
    <TableBody ref={provided.innerRef} {...provided.droppableProps}>
      {/** ドラッグするアイテム  */}
      {provided.placeholder}
    </TableBody>
  )}
</Droppable>

ドラッグしたコンポーネントをドロップするエリアは、Droppableで囲みます。
TableBody内をドロップ可能エリアに指定しています。

ドラッグするコンポーネントはDraggableで囲む
{rows.map((row, index) => (
  <Draggable
    draggableId={row.name}
    index={index}
    key={row.name}
  >
    {(provided) => (
      <TableRow
        ref={provided.innerRef}
        {...provided.draggableProps}
        {...provided.dragHandleProps}
      >
        <TableCell style={{minWidth: `${Math.floor(100/3)}vw`}}>{row.name}</TableCell>
        <TableCell style={{minWidth: `${Math.floor(100/3)}vw`}}>{row.index}</TableCell>
        <TableCell style={{minWidth: `${Math.floor(100/3)}vw`}}>{row.order}</TableCell> 
      </TableRow>
    )}
  </Draggable>
))}

ドラッグしたいコンポーネントは一つずつDraggableで囲みます。

Material-UIのテーブル行をドラッグしたときにデザインが崩れたため、minWidthを均等になるように指定しています。

ドラッグ後のアクションはonDragEndで指定
<DragDropContext onDragEnd={onDragEnd}>

DragDropContextのonDragEndで、ドラッグ後のアクションを指定できます。
テーブルの行を入れ替えたい場合、ここで入れ替えを行います。

const onDragEnd = (result: DropResult) => {
  const { source, destination } = result;
  if (!destination) {
    return;
  }
  const update = reorder(source.index, destination.index);
  setRows(update);
};

onDragEndの処理内容は、ざっくり説明すると次のような感じです。

  • ドロップ領域(destination)を外れた場合
    →何しない
  • ドロップ領域(destination)内
    →行をドラッグ前(source.index)からドラッグ後(destination.index)に移動
const reorder = (startIndex: number, endIndex: number) => {
  const result = Array.from(rows);
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);
  result.map((row, index) => (row.order = index));
  return result;
};

並び替えは、Array.prototype.splice()で要素を取り除いてから指定箇所に追加します。
その後、並び順でorderを採番しています。

まとめ:深く考えなければ簡単にできる

react-beautiful-dndはよくわからない箇所が多いので、あまり考えずにコピペで動かしてみてください。コピペで動けば、あとは簡単です。

このブログでは、【ConoHa WING】を使っています

このブログでは、【ConoHa WING】を使っています
わたし、稼げました。

このブログでは、プログラミングでの学びをノート代わりとして記事としています。
少しずつPVが増えてきて、先日Google AdSenseの収益が振り込まれました!

どれくらいの記事数、期間、PVがあれば振込ボーターの8千円に到達するのか?
私のリアルな数字を紹介します。

ブログ村を利用しています
素人エンジニアの苦悩 - にほんブログ村
PVアクセスランキング にほんブログ村
Material-UI React
スポンサーリンク
スポンサーリンク
シェアする
amateur_engineerをフォローする
素人エンジニアの苦悩

コメント

タイトルとURLをコピーしました