Kanban Board / Multi-List Drag and Drop with react-beautiful-dnd in Next.js

Abhishek kumar
6 min readMar 3, 2024

--

Drag-and-drop functionality is a fantastic way to enhance user experience, and with the help of the react-beautiful-dnd library, implementing it in your projects has never been easier. In this beginner-friendly guide, we’ll walk you through the basics of drag-and-drop and show you how to get started with react-beautiful-dnd.

What we are going to build ? Example

Drag-and-drop is a common user interface interaction that allows users to click and drag elements on the screen and drop them onto designated areas. This intuitive interaction pattern is widely used in applications such as task managers, file organizers, and content management systems.

Introducing react-beautiful-dnd

React-beautiful-dnd is a powerful library built specifically for React applications that makes it simple to add drag-and-drop functionality to your projects. It provides a set of components and utilities that handle the complex logic behind drag-and-drop interactions, allowing you to focus on building great user experiences.At its core, react-beautiful-dnd introduces three key concepts:

  1. Draggable: A Draggable component represents an item that can be dragged by the user. Each Draggable component is assigned a unique identifier (draggableId) and an index within its parent Droppable component.
  2. Droppable: A Droppable component defines an area where draggable items can be dropped. Droppable components can contain one or more draggable items and are assigned a unique identifier (droppableId).
  3. DragDropContext: The DragDropContext component serves as the wrapper for your entire application and is responsible for managing drag-and-drop interactions. It provides the necessary context for draggable and droppable components to communicate with each other.

Getting Started

1. Installation

Start by installing react-beautiful-dnd in your project using npm or yarn:

npm install react-beautiful-dnd

or

yarn add react-beautiful-dnd

2. Some configuration

If you are using Next.js then add resetServerContext() in the getServerSideProps where you are using the drag and drop. In the context of drag-and-drop functionality, it ensures that the server-side context is reset before each request, preventing any potential conflicts or inconsistencies related to server-side rendering.

export async function getServerSideProps(context) {
resetServerContext();
return {
props: {},
};
}

In the next.config file, switch the reactStrictMode to false.

3. Let’s dive deep into the code :

In a real-world scenario, implementing drag-and-drop functionality with react-beautiful-dnd can address various use cases, each involving different interactions with columns and items. Here are three common scenarios:

a) Changing Column Orders:

  • This scenario involves rearranging the order of columns themselves.
  • Users can drag a column and drop it in a different position within the list of columns.
  • Example: In a project management tool, users may want to reorder project stages such as “To Do,” “In Progress,” and “Done.”

b) Reordering Items within the Same Column:

  • In this case, users rearrange the order of items within a single column.
  • Users can drag an item and drop it in a different position within the same column.
  • Example: In a task management application, users may want to reorder tasks within a specific status column like “In Progress” by priority or due date.

c) Moving Items between Different Columns:

  • Here, users move items from one column to another, thereby changing the column association of the item.
  • Users can drag an item from its current column and drop it into a different column.
  • Example: In a kanban board application, users may want to move tasks from one stage (column) to another as they progress through the workflow.

To keep track of changes in the database while implementing drag-and-drop functionality using react-beautiful-dnd, we can maintain the following data structures:

1 . Column Order Array:

  • This array will store the order of columns. Each element in the array represents a column identifier.
  • Whenever the order of columns changes, this array will be updated accordingly.
  • Example: const columnOrder = ["column-1", "column-2", "column-3"];

2. Column Data Object:

  • This object will contain the data for each column, including the order of items within each column.
  • Each key-value pair represents a column identifier and its corresponding data.
  • Example:
const columnData = {
"column-1": {
id: "column-1",
title: "Column 1",
itemsOrder: ["item-1", "item-2", "item-3"],
},
"column-2": {
id: "column-2",
title: "Column 2",
itemsOrder: ["item-4", "item-5"],
},
// Add data for other columns...
};

3. Items Object:

  • This object will contain all the items present in the columns, with each item identified by a unique identifier.
  • Example :
const items = {
"item-1": { id: "item-1", title: "Item 1" },
"item-2": { id: "item-2", title: "Item 2" },
// Add data for other items...
};

Enough of the theory, let’s code finally 😅 :

import { useState } from "react";
import {
DragDropContext,
Draggable,
Droppable,
resetServerContext,
} from "react-beautiful-dnd";
import Column from "@/components/Column/Column";

const INITIAL_COLUMN_ORDER = ["column-1", "column-2", "column-3"];

const INITIAL_COL_DATA = {
"column-1": {
id: "column-1",
title: "Column 1",
itemsOrder: ["item-1", "item-2", "item-3"],
},
"column-2": {
id: "column-2",
title: "Column 2",
itemsOrder: ["item-4", "item-5"],
},
"column-3": {
id: "column-3",
title: "Column 3",
itemsOrder: ["item-6", "item-7", "item-8"],
},
};

const ITEMS = {
"item-1": {
id: "item-1",
title: "Item 1",
},
"item-2": {
id: "item-2",
title: "Item 2",
},
"item-3": {
id: "item-3",
title: "Item 3",
},
"item-4": {
id: "item-4",
title: "Item 4",
},
"item-5": {
id: "item-5",
title: "Item 5",
},
"item-6": {
id: "item-6",
title: "Item 6",
},
"item-7": {
id: "item-7",
title: "Item 7",
},
"item-8": {
id: "item-8",
title: "Item 8",
},
};

//add this if using next.js and keep the strict mode to false
export async function getServerSideProps(context) {
resetServerContext();
return {
props: {},
};
}

export default function Home() {
const [columnsOrder, setColumnsOrder] = useState(INITIAL_COLUMN_ORDER);
const [data, setData] = useState(INITIAL_COL_DATA);

const handleDragDrop = (results) => {
// we get the results from drag and drop
const { source, destination, type } = results;

if (!destination) return;

if (
source.droppableId === destination.droppableId &&
source.index === destination.index
)
return;

const sourceIndex = source.index;
const destinationIndex = destination.index;

if (type === "COLUMN") {
//dragging the columns
const reorderedColumns = [...columnsOrder];
const [removedItem] = reorderedColumns.splice(sourceIndex, 1);
reorderedColumns.splice(destinationIndex, 0, removedItem);

setColumnsOrder(reorderedColumns);
//save the reordered column in database

return;
} else {
//changes within same column

if (source.droppableId === destination.droppableId) {
const source_col_id = source.droppableId;
const new_items_id_collection = [...data[source_col_id].itemsOrder];
const [deleted_item_id] = new_items_id_collection.splice(
sourceIndex,
1
);
new_items_id_collection.splice(destinationIndex, 0, deleted_item_id);
const new_data = { ...data };
new_data[source_col_id].itemsOrder = new_items_id_collection;
setData(new_data);

//update the db

} else {
//changes within different col
const source_col_id = source.droppableId,
dest_col_id = destination.droppableId;

const new_source_items_id_collc = [...data[source_col_id].itemsOrder];
const new_dest_items_id_collc = [...data[dest_col_id].itemsOrder];
const [deleted_item_id] = new_source_items_id_collc.splice(
sourceIndex,
1
);

new_dest_items_id_collc.splice(destinationIndex, 0, deleted_item_id);
const new_data = { ...data };
new_data[source_col_id].itemsOrder = new_source_items_id_collc;
new_data[dest_col_id].itemsOrder = new_dest_items_id_collc;

setData(new_data);

//update the db
}
}
};

return (
<div className="flex h-full w-full items-center flex-col">
<p className="font-bold text-4xl bg-gradient-to-r from-purple-600 via-blue-400 to-indigo-400 mt-10 text-transparent bg-clip-text">
React Beautiful DND Example
</p>
{/* Set up DragDropContext */}
<DragDropContext onDragEnd={handleDragDrop}>
{/* Render Droppable area for columns */}
<Droppable droppableId="ROOT" type="COLUMN" direction="HORIZONTAL">
{(provided) => (
<div
className="flex items-center w-full md:max-w-6xl justify-center border min-h-96 py-4 mt-6 rounded-md overflow-x-scroll md:overflow-hidden"
{...provided.droppableProps}
ref={provided.innerRef}
>
{/* Map through columnsOrder to render each column */}
{columnsOrder.map((colId, index) => {
const columnData = data[colId];
return (
<Draggable
draggableId={columnData.id}
key={columnData.id}
index={index}
>
{(provided) => (
<div
className="rounded-md border flex flex-col max-w-xs mx-3"
ref={provided.innerRef}
{...provided.draggableProps}
>
<div
{...provided.dragHandleProps}
className="flex items-center justify-between w-80 gap-2 hover:bg-gray-600 p-4 border-b border-b-gray-700 rounded-t-md"
>
<p className="text-xl font-bold">
{columnData.title}
</p>
</div>

{/* Render items within the column */}
<Column {...columnData} ITEMS={ITEMS} />
</div>
)}
</Draggable>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
);
}

Column.jsx

import React from "react";
import { Draggable, Droppable } from "react-beautiful-dnd";

const Column = ({ itemsOrder, id, ITEMS }) => {
return (
<Droppable droppableId={id}>
{(provided) => (
<div
{...provided.draggableProps}
ref={provided.innerRef}
className="flex flex-col w-full min-h-60 h-fit"
>
{itemsOrder.map((item_id, index) => {
const item = ITEMS[item_id];

return (
<Draggable draggableId={item.id} index={index} key={item.id}>
{(provided) => (
<div
className="border-b rounded-md flex flex-col p-2 m-2 bg-pink-500"
{...provided.dragHandleProps}
{...provided.draggableProps}
ref={provided.innerRef}
>
<p className="font-bold text-lg ">{item.title}</p>
</div>
)}
</Draggable>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
);
};

export default Column;

Github repo : https://github.com/abhiiishek07/react-beautiful-dnd-example

Live Link : https://react-beautiful-dnd-example-brown.vercel.app/

--

--