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

ishigaki

こんにちは、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

投稿者 石垣 祥太郎

アシスタントエンジニアとしてHTML/CSS/JavaScriptの実装やRailsの組み込み、スタイルガイドの構築などを担当しています。 業務の中でさまざまな学びを吸収しながら、文書構造やアクセシビリティに目を向けたマークアップの学習やJavaScriptの学習などを行っています。チームに貢献できるエンジニアとなるために日々奮闘中です。