In this blog I'll be showing how to build a draggable todo list with React beautiful dnd. If you're not familiar with React beautiful dnd, it's an amazing library to implement drag and drop functionality in your React application. In this step by step tutorial we'll be building a simple todo list with the ability to reorder items and drag and drop items to another column. Let's start by setting up a basic React app and the necessary components from React beautiful dnd.
Installation
Let us first install our react app with vite, then install tailwind css framework.
Next install react-beautiful-dnd for our drag and drop functionality.
Components
Let's walk through our components.
Create a droppable
component to work with React Strict mode enabled.
details
Droppable Component
import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";
const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
const animation = requestAnimationFrame(() => setEnabled(true));
return () => {
cancelAnimationFrame(animation);
setEnabled(false);
};
}, []);
if (!enabled) {
return null;
}
return <Droppable {...props}>{children}</Droppable>;
};
export default StrictModeDroppable;
The Droppable component
defines a drop zone for draggable items.
Learn more about Droppable
Draggable Card
import { Draggable } from "react-beautiful-dnd";
interface CardProps {
id: number;
title: string;
draggableId: string;
index: number;
}
const Card = ({ title, draggableId, index }: CardProps) => {
return (
<Draggable draggableId={draggableId} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`${
snapshot.isDragging ? "bg-gray-100" : "bg-white"
} px-2 py-4 font-medium w-full h-24 shadow-md shadow-blue-300 rounded-md`}
>
{title}
</div>
)}
</Draggable>
);
};
export default Card;
We have a card component to show each task for our todo list which is wrapped in a Draggable component
which is used to define a draggable item in a drag-and-drop context. It requires a draggableId and index to be passed as props. Learn more about Draggable
Column Component
import Card from "./card";
interface ItemsColumnProps {
columnTitle: string;
items: { id: number; title: string }[];
}
const ItemsColumn = ({ columnTitle, items }: ItemsColumnProps) => {
return (
<div
className="h-[392px] scrollbar-thin scrollbar-thumb-blue-700
scrollbar-track-blue-300 overflow-y-auto
p-4 rounded-md border border-blue-300"
>
<p className="inline-block py-1 px-2 text-lg font-semibold bg-blue-300 rounded-md">
{columnTitle}
</p>
<div className=" pt-4 flex flex-col gap-y-3">
{items &&
items.length > 0 &&
items.map((item, index) => (
<Card
key={item.id}
draggableId={item.id.toString()}
index={index}
id={item.id}
title={item.title}
/>
))}
</div>
</div>
);
};
export default ItemsColumn;
Define interfaces and initial data
interface ITodoItem {
id: number;
title: string;
}
interface ColumnItem {
id: number;
title: string;
items: {
id: number;
title: string;
}[];
}
type ColumnType = { [key: string]: ColumnItem };
const initialTodoItems = [
{
id: 1,
title: "Go for a walk",
},
{
id: 2,
title: "Take a nap",
},
{
id: 3,
title: "Read a book",
},
{
id: 4,
title: "Work out",
},
{
id: 5,
title: "Learn something new",
},
];
const initialColumnData = {
todoColumn: {
id: 1,
title: "To do",
items: [...initialTodoItems],
},
doneColumn: {
id: 2,
title: "Done",
items: [],
},
};
Render the list component
import { useState } from "react";
import { DragDropContext, DropResult } from "react-beautiful-dnd";
import ItemsColumn from "./itemsColumn";
import Droppable from "./droppable";
const TodoList = () => {
const [columnData, setColumnData] = useState<ColumnType>(initialColumnData);
const onDragEnd = (result: DropResult) => {
}
return (
<div className="w-[800px] mx-auto">
<p className="py-12 text-3xl text-center font-semibold text-blue-800">
Todo List
</p>
<div className="grid grid-cols-2 gap-x-4 justify-between">
<DragDropContext onDragEnd={onDragEnd}>
{Object.entries(columnData).map(([id, column]) => (
<Droppable droppableId={id} key={id}>
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
<ItemsColumn
columnTitle={column.title}
items={column.items}
/>
{provided.placeholder}
</div>
)}
</Droppable>
))}
</DragDropContext>
</div>
</div>
);
};
export default TodoList;
DragDropContext
wraps a Droppable
component and enables drag drop functionality. Learn more about DragDropContext
Functions for re ordering and dropping elements
const reorder = (
list: Array<ITodoItem>,
startIndex: number,
endIndex: number
) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const onDragEnd = (result: DropResult) => {
const { source, destination } = result;
// dropped outside the list
if (!result.destination) {
return;
}
const sInd = source.droppableId;
const dInd = destination?.droppableId;
// REORDER: if source and destination droppable ids are same
if (dInd && sInd === dInd) {
const column = columnData[sInd];
const reorderedItems = reorder(
column.items,
source.index,
destination.index
);
setColumnData({
...columnData,
[dInd]: {
...column,
items: reorderedItems,
},
});
}
// DROP: if source and destination droppable ids are different
if (dInd && dInd !== sInd) {
const sourceColumn = columnData[sInd];
const desColumn = columnData[dInd];
const itemToDrop = sourceColumn.items.find(
(item) => item.id.toString() == result.draggableId
);
//INSERT: dragged item to another column
if (itemToDrop) {
const sourceColumnItems = Array.from(sourceColumn.items);
const destColumnItems = Array.from(desColumn.items);
sourceColumnItems.splice(result.source.index, 1);
destColumnItems.splice(result.destination.index, 0, itemToDrop);
setColumnData({
...columnData,
[sInd]: {
...sourceColumn,
items: sourceColumnItems,
},
[dInd]: {
...desColumn,
items: destColumnItems,
},
});
}
}
};
onDragEnd
function checks if the item being dragged is dropped in a valid droppable and reorders or drops upon checking the source and destination droppableId
.
Top comments (4)
Very nice post! Keep going!
Thank you! Sure will come up with more posts soon.
Nice Work!
Could you share the full codebase in github or live preview if there's any?
You can check out this repository
github.com/TasfiaIslam/Draggable-todo