Before we begin, make sure you have the following prerequisites:
- A Next.js application. If you don't have one, you can create a new Next.js app using the following command:
npx create-next-app my-app
-
Chakra UI configured on the Next.js application. You can follow this link to set it up: Next.js Chakra installation guide
-
Configure a Chakra UI theme for the application. You can do that by creating a
theme.js
file in thesrc
folder and pasting the following code:
// src/theme.ts
import { extendTheme, theme } from "@chakra-ui/react";
const colors = {
"main-bg": "#0E1012",
"white-text": "#E8E8EA",
"subtle-text": "#9B9B9B",
"column-bg": "#16181D",
"column-header-bg": "#1A1D23",
"card-bg": "#242731",
"card-border": "#2D313E"
};
const fonts = {
heading: "Poppins",
body: "Poppins",
};
export default extendTheme({
...theme,
colors,
fonts,
});
Make sure you update _app.js
accordingly.
Having addressed the prerequisites, let's move on to designing the page in the pages/index.js
file.
But before that, you need to install react-beautiful-dnd
by running the command:
yarn add react-beautiful-dnd
Setting up the context
The first step in using react-beautiful-dnd
is to wrap the elements that will handle the drag and drop in a DragDropContext
component. This component provides the context that the Draggable
and Droppable
components will use to interact with each other. Here's an example of how to set up the DragDropContext
:
// pages/index.js
import { DragDropContext } from 'react-beautiful-dnd';
function Home() {
return (
<DragDropContext onDragEnd={onDragEnd}>
{/* Your columns go here */}
</DragDropContext>
);
}
You need to pass an onDragEnd
callback function that will be called when a drag-and-drop event is completed. In this callback function, you can update the state of your application with the new order of the items.
function onDragEnd(result) {
// Update the state of your application with the new order of the items
// after a drag-and-drop event
}
Creating a droppable area
The next step is to create a droppable area where items can be dropped. This is done using the Droppable component. The Droppable component takes a droppableId
prop that is used to identify the droppable area.
Inside our pages/index.js
, we are going to update the component by adding code for the droppable area:
// pages/index.js
import { useState } from 'react';
import { Flex, Heading, Text } from "@chakra-ui/react";
import { Droppable } from 'react-beautiful-dnd';
function Home() {
const [state, setState] = useState(initialData);
function onDragEnd(result) {
// Update the state of your application with the new order of the items
// after a drag-and-drop event
}
return (
<DragDropContext onDragEnd={onDragEnd}>
<Flex
flexDir="column"
bg="main-bg"
minH="100vh"
w="full"
color="white-text"
pb="2rem"
>
<Flex py="4rem" flexDir="column" align="center">
<Heading fontSize="3xl" fontWeight={600}>
React Beautiful Drag and Drop
</Heading>
<Text fontSize="20px" fontWeight={600} color="subtle-text">
react-beautiful-dnd
</Text>
</Flex>
<Flex justify="space-between" px="4rem">
{state.columnOrder.map((columnId) => {
const column = state.columns[columnId];
const tasks = column.taskIds.map((taskId) => state.tasks[taskId]);
return <Droppable droppableId={column.id}>
{(droppableProvided, droppableSnapshot) => (
{/* Your droppable code here */}
)}
</Droppable >
})}
</Flex>
</Flex>
</DragDropContext>
);
}
const initialData = {
tasks: {},
columns: {
"column-1": {},
"column-2": {},
"column-3": {},
},
// Facilitate reordering of the columns
columnOrder: ["column-1", "column-2", "column-3"],
};
Creating draggable items
The final step is to make the items in the list draggable. This is done using the Draggable
component. The Draggable component takes a draggableId
prop that is used to identify the item and an index
prop that is used to specify the item's position in the list.
To do this, we are going to create a Column
component to render the TODO
, IN-PROGRESS
, and the COMPLETED
columns respectively.
// src/components/Column.js
import { Flex, Text } from "@chakra-ui/react";
import React from "react";
import { Draggable, Droppable } from "react-beautiful-dnd";
const Column = ({ column, tasks }) => {
return (
<Flex rounded="3px" bg="column-bg" w="400px" h="620px" flexDir="column">
<Flex
align="center"
h="60px"
bg="column-header-bg"
rounded="3px 3px 0 0"
px="1.5rem"
mb="1.5rem"
>
<Text fontSize="17px" fontWeight={600} color="subtle-text">
{column.title}
</Text>
</Flex>
<Droppable droppableId={column.id}>
{(droppableProvided, droppableSnapshot) => (
<Flex
px="1.5rem"
flex={1}
flexDir="column"
ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps}
>
{tasks.map((task, index) => (
<Draggable key={task.id} draggableId={`${task.id}`} index={index}>
{(draggableProvided, draggableSnapshot) => (
<Flex
mb="1rem"
h="72px"
bg="card-bg"
rounded="3px"
p="1.5rem"
outline="2px solid"
outlineColor={
draggableSnapshot.isDragging
? "card-border"
: "transparent"
}
boxShadow={
draggableSnapshot.isDragging
? "0 5px 10px rgba(0, 0, 0, 0.6)"
: "unset"
}
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
>
<Text>{task.content}</Text>
</Flex>
)}
</Draggable>
))}
</Flex>
)}
</Droppable>
</Flex>
);
};
export default Column;
In the above code, we're using the map function to loop through the tasks and creating a Draggable component for each task. We're also using the key
and index
props to identify the item and its position in the list.
To wrap up, at the end your pages/index.js
should have the following code:
// pages/index.js
import { Flex, Heading, Text } from "@chakra-ui/react";
import dynamic from "next/dynamic";
import React, { useState } from "react";
import { DragDropContext } from "react-beautiful-dnd";
const Column = dynamic(() => import("../src/Column"), { ssr: false });
const reorderColumnList = (sourceCol, startIndex, endIndex) => {
const newTaskIds = Array.from(sourceCol.taskIds);
const [removed] = newTaskIds.splice(startIndex, 1);
newTaskIds.splice(endIndex, 0, removed);
const newColumn = {
...sourceCol,
taskIds: newTaskIds,
};
return newColumn;
};
export default function Home() {
const [state, setState] = useState(initialData);
const onDragEnd = (result) => {
const { destination, source } = result;
// If user tries to drop in an unknown destination
if (!destination) return;
// if the user drags and drops back in the same position
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
// If the user drops within the same column but in a different positoin
const sourceCol = state.columns[source.droppableId];
const destinationCol = state.columns[destination.droppableId];
if (sourceCol.id === destinationCol.id) {
const newColumn = reorderColumnList(
sourceCol,
source.index,
destination.index
);
const newState = {
...state,
columns: {
...state.columns,
[newColumn.id]: newColumn,
},
};
setState(newState);
return;
}
// If the user moves from one column to another
const startTaskIds = Array.from(sourceCol.taskIds);
const [removed] = startTaskIds.splice(source.index, 1);
const newStartCol = {
...sourceCol,
taskIds: startTaskIds,
};
const endTaskIds = Array.from(destinationCol.taskIds);
endTaskIds.splice(destination.index, 0, removed);
const newEndCol = {
...destinationCol,
taskIds: endTaskIds,
};
const newState = {
...state,
columns: {
...state.columns,
[newStartCol.id]: newStartCol,
[newEndCol.id]: newEndCol,
},
};
setState(newState);
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Flex
flexDir="column"
bg="main-bg"
minH="100vh"
w="full"
color="white-text"
pb="2rem"
>
<Flex py="4rem" flexDir="column" align="center">
<Heading fontSize="3xl" fontWeight={600}>
React Beautiful Drag and Drop
</Heading>
<Text fontSize="20px" fontWeight={600} color="subtle-text">
react-beautiful-dnd
</Text>
</Flex>
<Flex justify="space-between" px="4rem">
{state.columnOrder.map((columnId) => {
const column = state.columns[columnId];
const tasks = column.taskIds.map((taskId) => state.tasks[taskId]);
return <Column key={column.id} column={column} tasks={tasks} />;
})}
</Flex>
</Flex>
</DragDropContext>
);
}
const initialData = {
tasks: {
1: { id: 1, content: "Configure Next.js application" },
2: { id: 2, content: "Configure Next.js and tailwind " },
3: { id: 3, content: "Create sidebar navigation menu" },
4: { id: 4, content: "Create page footer" },
5: { id: 5, content: "Create page navigation menu" },
6: { id: 6, content: "Create page layout" },
},
columns: {
"column-1": {
id: "column-1",
title: "TO-DO",
taskIds: [1, 2, 3, 4, 5, 6],
},
"column-2": {
id: "column-2",
title: "IN-PROGRESS",
taskIds: [],
},
"column-3": {
id: "column-3",
title: "COMPLETED",
taskIds: [],
},
},
// Facilitate reordering of the columns
columnOrder: ["column-1", "column-2", "column-3"],
};
You can now run the command below to test the app.
yarn dev
You should have something similar to the screenshot below.
Conclusion
react-beautiful-dnd
is a powerful and easy-to-use library for implementing drag-and-drop functionality in a React application. By following the steps outlined in this article, you can quickly add drag-and-drop functionality to a list of items in your application.
If you found this tutorial helpful and want to stay up-to-date with the latest web development trends and techniques, be sure to subscribe to our newsletter or our channel at FullStack Matery. We will notify you of new articles, tech updates, and early access to exclusive content. Don't miss out on the opportunity to grow as a developer and stay ahead of the curve!
Lastly, let me know what you think about the article by leaving a comment below.
Thanks!