この記事ではreact-beautiful-dndを使って要素の順序をドラッグ&ドロップで入れ替える方法をご紹介します。
react-beautiful-dndを使うと以下のデモのようにドラッグ&ドロップで質問の順序を入れ替えることができます。
react-beautiful-dndはyarnまたはnpmを使ってインストールします。
yarn add react-beautiful-dnd
npm install --save react-beautiful-dnd
基本的な使い方
react-beautiful-dndを使ったデモのソースコードは以下のとおりです。このソースコードがreact-beautiful-dndの基本的な使い方となります。
import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
// ドラッグ&ドロップした要素を入れ替える
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
// ドラッグ&ドロップの質問のスタイル
const getItemStyle = (isDragging, draggableStyle) => ({
background: isDragging ? "#757ce8" : "white",
...draggableStyle
});
// ドラッグ&ドロップのリストのスタイル
const getListStyle = (isDraggingOver) => ({
background: isDraggingOver ? "#1769aa" : "lightgrey",
padding: "10px"
});
export default () => {
const [questions, setQuestions] = useState([
{ id: 1, title: "question1" },
{ id: 2, title: "question2" },
{ id: 3, title: "question3" },
{ id: 4, title: "question4" },
{ id: 5, title: "question5" }
]);
const onDragEnd = (result) => {
// ドロップ先がない
if (!result.destination) {
return;
}
// 配列の順序を入れ替える
let movedItems = reorder(
questions, // 順序を入れ変えたい配列
result.source.index, // 元の配列の位置
result.destination.index // 移動先の配列の位置
);
setQuestions(movedItems);
};
return (
// ドラッグアンドドロップの有効範囲
<DragDropContext onDragEnd={onDragEnd}>
{/* ドロップできる範囲 */}
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
{/* ドラッグできる要素 */}
{questions.map((question, index) => (
<Draggable
key={question.id}
draggableId={"q-" + question.id}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
{question.title}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
ドラッグ&ドロップの有効範囲
ドラッグできる要素とドロップできる範囲は<DragDropContext />の中だけになります。
そのため、<DragDropContext />の中で<Droppable />や<Draggable />を使用します。
ドロップできる範囲
<Droppable />はドラッグした要素をドロップできる範囲です。<Droppable />で指定した範囲にドロップした場合に要素の順序を入れ替えれます。
<Droppable />で指定した範囲外にドロップした場合は順序は入れ替わりません。
ドラッグできる要素
<Draggable />はドラッグしたい要素をラップします。ドラッグ時は<Draggable />の範囲の要素を移動させることができます。
ドラッグ&ドロップ中のスタイルを変更
react-beautiful-dndはドラッグ&ドロップ中のスタイルを変更することができます。
デモのソースコードでは以下の部分でスタイルを変更しています。
// ドラッグ&ドロップの質問のスタイル
const getItemStyle = (isDragging, draggableStyle) => ({
background: isDragging ? "#757ce8" : "white",
...draggableStyle
});
// ドラッグ&ドロップのリストのスタイル
const getListStyle = (isDraggingOver) => ({
background: isDraggingOver ? "#1769aa" : "lightgrey",
padding: "10px"
});
...
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
...
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
...
getItemStyle()でドラッグ中の要素のスタイルを変更をして、getListStyle()でリスト全体のスタイルを変更しています。
provided.placeholder
<Droppable />の中のドロップ可能な範囲の中に{provided.placeholder}を入れる必要があります。
provided.placeholderはドラッグした時の空白を作ってくれます。
draggableId
デモのソースコードでは<Draggable />でdraggableIdを以下の様に指定しました。
...
<Draggable
key={question.id}
draggableId={"q-" + question.id}
index={index}
>
...
draggableIdを文字ではなく数字で指定すると次のエラーが出てしまします。
react-beautiful-dnd
A setup problem was encountered.
> Invariant failed: Draggable requires a [string] draggableId.
Provided: [type: number] (value: 5)
このエラーが出たらdraggableIdを数字ではなく文字にしてエラーを解消してください。
typescriptでreact-beautiful-dndを実装
typescriptでreact-beautiful-dndを使う方法をご紹介します。
typescriputで書くにはまず@types/react-beautiful-dndをインストールします。
yarn add @types/react-beautiful-dnd
npm install --save @types/react-beautiful-dnd
デモのソースコードをtypescriptに書き換えたコードは次のとおりです。
import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import type {
DropResult,
DraggingStyle,
NotDraggingStyle,
DroppableProvided,
DroppableStateSnapshot,
DraggableProvided,
DraggableStateSnapshot
} from "react-beautiful-dnd";
type questionType = { id: number; title: string };
// ドラッグ&ドロップした要素を入れ替える
const reorder = (
list: questionType[],
startIndex: number,
endIndex: number
): questionType[] => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
// ドラッグ&ドロップの質問のスタイル
const getItemStyle = (
isDragging: boolean,
draggableStyle: DraggingStyle | NotDraggingStyle | undefined
) => ({
background: isDragging ? "#757ce8" : "white",
...draggableStyle
});
// ドラッグ&ドロップのリストのスタイル
const getListStyle = (isDraggingOver: boolean) => ({
background: isDraggingOver ? "#1769aa" : "lightgrey",
padding: "10px"
});
export default () => {
const [questions, setQuestions] = useState([
{ id: 1, title: "question1" },
{ id: 2, title: "question2" },
{ id: 3, title: "question3" },
{ id: 4, title: "question4" },
{ id: 5, title: "question5" }
]);
const onDragEnd = (result: DropResult) => {
// ドロップ先がない
if (!result.destination) {
return;
}
// 配列の順序を入れ替える
let movedItems = reorder(
questions, // 順序を入れ変えたい配列
result.source.index, // 元の配列の位置
result.destination.index // 移動先の配列の位置
);
setQuestions(movedItems);
};
return (
// ドラッグアンドドロップの有効範囲
<DragDropContext onDragEnd={onDragEnd}>
{/* ドロップできる範囲 */}
<Droppable droppableId="droppable">
{(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
{/* ドラッグできる要素 */}
{questions.map((question, index) => (
<Draggable
key={question.id}
draggableId={"q-" + question.id}
index={index}
>
{(
provided: DraggableProvided,
snapshot: DraggableStateSnapshot
) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
{question.title}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
material uiを使ってレイアウトする
material uiとreact-beautiful-dndを使って並び替えする方法をご紹介します。
まずは次のようにしてmaterial uiをインストールします。
yarn add @mui/material
yarn add @emotion/styled
yarn add @emotion/react
npm install --save @mui/material
npm install --save @emotion/styled
npm install --save @emotion/react
Cardで並び替えする
material uiのCardを使って並び替えするデモは次のとおりです。
このデモのソースコードは次のとおりです。
import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
// ドラッグ&ドロップした要素を入れ替える
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
// ドラッグ&ドロップの質問のスタイル
const getItemStyle = (isDragging, draggableStyle) => ({
background: isDragging ? "#757ce8" : "white",
marginBottom: "5px",
...draggableStyle
});
// ドラッグ&ドロップのリストのスタイル
const getListStyle = (isDraggingOver) => ({
background: isDraggingOver ? "#1769aa" : "lightgrey",
padding: "10px"
});
export default () => {
const [questions, setQuestions] = useState([
{ id: 1, title: "question1" },
{ id: 2, title: "question2" },
{ id: 3, title: "question3" },
{ id: 4, title: "question4" },
{ id: 5, title: "question5" }
]);
const onDragEnd = (result) => {
// ドロップ先がない
if (!result.destination) {
return;
}
// 配列の順序を入れ替える
let movedItems = reorder(
questions, // 順序を入れ変えたい配列
result.source.index, // 元の配列の位置
result.destination.index // 移動先の配列の位置
);
setQuestions(movedItems);
};
return (
<>
{/*ドラッグアンドドロップの有効範囲 */}
<DragDropContext onDragEnd={onDragEnd}>
{/* ドロップできる範囲 */}
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
{/* ドラッグできる要素 */}
{questions.map((question, index) => (
<Draggable
key={question.id}
draggableId={"q-" + question.id}
index={index}
>
{(provided, snapshot) => (
<Card
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<CardContent>
<Typography variant="h5" component="div">
{question.title}
</Typography>
</CardContent>
</Card>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</>
);
};
ドラッグする要素をカードにするため、<Draggable />の中にカードコンポーネントを記述します。
Tableで並び替えをする
material uiのTableを使って並び替えするデモは次のとおりです。
このデモのソースコードは次のとおりです。
import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
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";
import DragHandleIcon from "@mui/icons-material/DragHandle";
// ドラッグ&ドロップした要素を入れ替える
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
// ドラッグ&ドロップの質問のスタイル
const getItemStyle = (isDragging, draggableStyle) => ({
background: isDragging ? "#757ce8" : "white",
...draggableStyle
});
// ドラッグ&ドロップのリストのスタイル
// const getListStyle = (isDraggingOver) => ({
// background: isDraggingOver ? "#1769aa" : "lightgrey",
// padding: "10px"
// });
export default () => {
const [questions, setQuestions] = useState([
{ id: 1, title: "question1" },
{ id: 2, title: "question2" },
{ id: 3, title: "question3" },
{ id: 4, title: "question4" },
{ id: 5, title: "question5" }
]);
const onDragEnd = (result) => {
// ドロップ先がない
if (!result.destination) {
return;
}
// 配列の順序を入れ替える
let movedItems = reorder(
questions, // 順序を入れ変えたい配列
result.source.index, // 元の配列の位置
result.destination.index // 移動先の配列の位置
);
setQuestions(movedItems);
};
return (
<TableContainer component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>question</TableCell>
<TableCell>handle</TableCell>
</TableRow>
</TableHead>
{/* <TableBody> */}
{/*ドラッグアンドドロップの有効範囲 */}
<DragDropContext onDragEnd={onDragEnd}>
{/* ドロップできる範囲 */}
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<TableBody
{...provided.droppableProps}
ref={provided.innerRef}
// style={getListStyle(snapshot.isDraggingOver)}
>
{/* ドラッグできる要素 */}
{questions.map((question, index) => (
<Draggable
key={question.id}
draggableId={"q-" + question.id}
index={index}
>
{(provided, snapshot) => (
<TableRow
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<TableCell component="th" scope="row">
{question.id}
</TableCell>
<TableCell>{question.title}</TableCell>
<TableCell>
<DragHandleIcon />
</TableCell>
</TableRow>
)}
</Draggable>
))}
{provided.placeholder}
</TableBody>
)}
</Droppable>
</DragDropContext>
{/* </TableBody> */}
</Table>
</TableContainer>
);
};
まとめ
この記事ではreact-beautiful-dndを使って要素の順序をドラッグ&ドロップで入れ替える方法をご紹介しました。
ご紹介した方法以外にもサンプル集も非常に参考になります。サンプル集のソースコードはこちらです。
この記事の参考書をまとめておきます。
コメント