Material-UI + react-smooth-dnd でドラッグ&ドロップ可能なリストを作成する


2021年10月7日追記:初版の記事タイトルが誤っているというご指摘を受けたため、修正いたしました。

こんにちは、Gaji-Labo アシスタントエンジニアの石垣です。

今回は、React 用 UI フレームワークである Material-UI と、ドラッグ&ドロップ可能なリストを作成できるライブラリ react-smooth-dnd でリストを実装する機会があったため、その実装方法についてまとめてみようと思います。

概要

Material Design のコンポーネントの一つにドラッグ&ドロップ可能なリストが定義されていますが、Material-UI にはデフォルトではそのコンポーネントは用意されていません。

そのため、別途プラグインを導入して独自に実装する必要がありました。

いくつかプラグインの候補がありましたが、今回は以下の条件を満たしているかどうかでプラグインを決めました。

  • ある程度プラグイン自体がアニメーションなどの機能を持っており、実装の負担が少ない
  • 軸の固定を行うかなど、設定が充実している
    • react-beautiful-dnd では軸の固定がプラグインのみでは実現できなかったため、候補から外しました。
  • ドラッグ時に元のコンポーネントのスタイルが保持される
    • react-sortable-hoc ではドラッグ時にスタイルの付いていないDOMが複製され、見た目が崩れてしまうため候補から外しました。

結果的に、react-smooth-dnd というパッケージを導入して実装することにしました。

実装方法

1. 基本となるコンポーネントを作成

まず、配列を受け取りコンポーネントを表示するコンポーネントを実装しました。

import React, { useState, ReactElement } from "react";
import { Box, TextField } from "@material-ui/core/";
import DragHandleIcon from "@material-ui/icons/DragHandle";

export function DraggableInputContainer(): ReactElement | null {
  const [items, setItems] = useState([
    {
      content: <TextField label="TextField 1" variant="outlined" />,
    },
    {
      content: <TextField label="TextField 2" variant="outlined" />,
    },
    {
      content: <TextField label="TextField 3" variant="outlined" />,
    },
  ]);

  return (
    <>
      {items.map(({ content }, i) => {
        return (
          <Box
            display="flex"
            justifyContent="space-between"
            alignItems="center"
            padding="8px"
            key={`DraggableInputContainer-${i}`}
          >
            {content}
            <DragHandleIcon />
          </Box>
        );
      })}
    </>
  );
}

配列の中身には TextField コンポーネントを入れています。

TextField の配列を受け取って表示するコンポーネントを実装した状態

ドラッグ用にアイコンを追加していますが、この時点ではまだドラッグで移動させることはできません。

2. react-smooth-dnd のコンポーネントを追加

import React, { useState, ReactElement } from "react";
...
import { Container, Draggable, DropResult } from "react-smooth-dnd";
import arrayMove from "array-move"; // 配列移動に使用するライブラリ

export function DraggableInputContainer(): ReactElement | null {
  const [items, setItems] = ...

  const onDrop = (dropResult: DropResult) => { // `DropResult` で型定義
    const { removedIndex, addedIndex } = dropResult;
    setItems((itemsArray) =>
      arrayMove(itemsArray, removedIndex || 0, addedIndex || 0)
    );
  };

  return (
    <Container
      dragHandleSelector=".dragHandleSelector" // ドラッグ用コンポーネントのセレクタを指定
      lockAxis="y" // 軸の固定を指定
      onDrop={onDrop} // ドラッグ時に呼ばれる関数、dropResult が渡る
    >
      {items.map(({ content }, i) => {
        return (
          <Draggable key={`DraggableInputContainer-${i}`}>
            <Box
              display="flex"
              justifyContent="space-between"
              alignItems="center"
              padding="8px"
            >
              {content}
              <DragHandleIcon className="dragHandleSelector" />
            </Box>
          </Draggable>
        );
      })}
    </Container>
  );
}

Container コンポーネントで全体を囲み、その中の移動させたい要素を Draggable コンポーネントで囲みます。Container コンポーネントの子には Draggable しか追加できない点に注意が必要です。

onDrop で配列の移動時に呼ばれる関数を定義することで、ドラッグ&ドロップ可能なリストを作成することができます。

dragHandleSelector lockAxis onDrop 以外の props については公式のドキュメントを参照ください。

ハンドルアイコンをドラッグして移動できるようになった状態

以上でドラッグ&ドロップ可能なリストの実装をすることができました。

まとめ

今回は、React 用 UI フレームワークである Material-UI と、react-smooth-dnd を使用してドラッグ&ドロップ可能なリストを実装する方法についてまとめました。

ドラッグ&ドロップ可能なリストの実装の際に参考にしていただければと思います。

Gaji-Laboでは、JavaScriptフレームワーク経験が豊富なパートナーさんを募集しています

Gaji-Laboでは、開発チームの一員としてプロジェクトに一緒に取り組んでくれる業務委託のパートナーさんを募集しています。

現在は特にJavaScriptフレームワーク実践と業務経験が豊富なWebフロントエンドエンジニアを必要としています。React + TypeScript、Vue.js、Next.js、Nuxt.js など、あなたの得意なフレームワークを教えて下さい!

パートナー契約へのお問い合わせもお仕事へのお問い合わせも、どちらもいつでも大歓迎です。まずはオンラインでのリモート面談からはじめましょう。ぜひお気軽にお問い合わせください!

お問い合わせしてみる!

投稿者 Ishigaki Shotaro

未経験から Gaji-Labo に入社。現在は React/TypeScript/Next.js の案件で MUI を使ったコンポーネント組み込みを担当。プロジェクトチームのリードとして共に組み込み作業をしているメンバーの進行管理も行っています。休日はだいたい家で音楽を聴いており、たまにライブに出かけています。