この記事は GMOアドマーケティング Advent Calendar 2023 12日目の記事です。
はじめに
はじめまして。GMOアドパートナーズの樋笠です。
最近Reactを使い始めたので、練習のために簡単なTodoアプリを作りました。
せっかくなら、ドラッグ&ドロップでタスクを並び替えられるようにしようと思いました。
dnd kit というライブラリを使うと簡単に実装できたので、ご紹介します。
Todoアプリの概要
今回は、 Mantine というUIライブラリを使用して作成しました。
MantineはボタンなどのUIコンポーネントだけでなく、便利なhooksを提供しています。
TodoListコンポーネント
まずは、アプリの大枠部分↑です。
useListState というhookを使って、タスクの追加・編集・削除などを行えるようにしています。
今回はイベントハンドラなどの細かい実装は省略しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import { Box, Button } from "@mantine/core"; import { useListState } from "@mantine/hooks"; import TodoItem from "./TodoItem"; const initialData = [ { id: 1, task: "洗剤購入", done: false }, { id: 2, task: "週報作成", done: false }, { id: 3, task: "洗い物", done: false }, { id: 4, task: "日記を書く", done: false }, ]; let lastId = 4; function TodoList() { const [todos, handlers] = useListState(initialData); return ( <> <Box w={400}> <Button fullWidth size="md" mb="xs"> 追加 </Button> {todos.map((todo) => ( <TodoItem key={todo.id} /> ))} </Box> </> ); } export default TodoList; |
TodoItemコンポーネント
それぞれのタスクを1つのコンポーネントとして実装しました。ドラッグできるようにしたい部分ですね。
ドラッグ時に掴む持ち手部分もアイコンを利用して実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import { Box, CloseButton, Checkbox, Group, Paper } from "@mantine/core"; import { RxDragHandleDots2 } from "react-icons/rx"; const TodoItem = () => { return ( <Paper shadow="sm" radius="sm" mt="4px" p="sm" bg={"white"} withBorder> <Group h={42}> <div> // ドラッグの持ち手 <RxDragHandleDots2 /> </div> <Checkbox /> <Box w="64%"> ... </Box> <CloseButton ml="sm" /> </Group> </Paper> ); }; export default TodoItem; |
並び替えの実装
では実際にドラッグして並び替えられるようにしていきます!
TodoListの修正
先ほどのTodoItemコンポーネントを DndContext と SortableContext の中に入れます。
1 2 3 4 5 6 7 8 9 10 11 |
<DndContext collisionDetection={closestCenter} modifiers={[restrictToVerticalAxis]} onDragEnd={handleDragEnd} > <SortableContext items={todos}> {todos.map((todo) => ( <TodoItem key={todo.id} /> ))} </SortableContext> </DndContext>; |
ドラッグやドロップを行うには、DndContext でコンポーネントをネストすることが必要です。
また、DndContext に渡すPropsで、衝突判定やドラッグの方向など細かい設定を行えます。
今回は、推奨されている closestCenter という衝突判定と restrictToVerticalAxis で縦方向にドラッグを固定しています。
SortableContext は今回のように並び替えを行いたい場合に使えるコンポーネントです。
もちろん、自分でドラッグ/ドロップしたいコンポーネントをカスタマイズすることもできます。
また、handleDragEnd には、ドラッグを終えた時の挙動を記述します。
今回は、タスクがきちんと並び替わるようにすれば良いですね。
DragEndEventから得られる、ドラッグされたコンポーネント active とドロップされた場所のコンポーネント over を利用します。
1 2 3 4 5 6 7 8 |
const handleDragEnd = (e: DragEndEvent) => { const { active, over } = e; if (over == null || active.id === over.id) return; const from = todos.findIndex((todo) => todo.id === active.id); const to = todos.findIndex((todo) => todo.id === over.id); handlers.reorder({ from, to }); }; |
TodoItemの修正
次に、TodoItemに修正を加えていきます。
useSortable というhookを利用して、ドラッグ&ドロップに必要なオブジェクトを受け取ります。
それぞれを対応するコンポーネントに渡せば、並び替えが可能になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
const TodoItem = () => { const { transform, transition, isDragging, // ドラッグの持ち手に渡す attributes, listeners, setActivatorNodeRef, // ドラッグしたいコンポーネントに渡す setNodeRef, } = useSortable({ id: id }); const draggableStyle = { transform: CSS.Transform.toString(transform), transition }; return ( <Paper // ドラッグしたいコンポーネント shadow="sm" radius="sm" mt="4px" p="sm" bg={"white"} withBorder style={draggableStyle} ref={setNodeRef} > <Group h={42}> <div // ドラッグの持ち手 ref={setActivatorNodeRef} {...attributes} {...listeners} style={{ cursor: isDragging ? "grabbing" : "grab" }} > <RxDragHandleDots2 /> </div> <Box w="64%">...</Box> <CloseButton ml="sm" /> </Group> </Paper> ); }; |
transform と transition からドラッグ時のアニメーションを作成しています。
isDragging はドラッグ中かどうかを示すflagです。それに応じてドラッグの持ち手の表示を変えています。
まとめ
dnd kit を利用して、簡単に並び替えを実装してみました。
よくあるカンバン形式のタスク管理アプリがどう実装されているのかを、少し理解できたのではないかと思います!
告知
明日はY.Gさんによる「【Adobe Photoshop】AIを活用してクリスマスカードを作る」です。
引き続き、GMOアドマーケティング Advent Calendar 2023 をお楽しみください!
■採用ページはこちら!
https://recruit.gmo-ap.jp/
■GMOアドパートナーズ 公式noteはこちら!
https://note.gmo-ap.jp/