DEV Community

Cover image for Drag and Drop Kanban board from scratch with React πŸ”₯
Ahmed
Ahmed

Posted on • Updated on

Drag and Drop Kanban board from scratch with React πŸ”₯

We all used those Kanban-style todo or task management apps where there are columns for each stages of a tasks and we can drag and drop tasks from one column to another.

Implementing this drag and drop feature in react is way easier than I thought. Everything you need is already there without using any library or package. So let's get started. πŸš€

*** ( Typescript is used but its not noticable in code examples JS users can easily understand and the CSS styles are omited in the code examples. Get to the github link to see the full code. )

The idea πŸ’‘

The main idea behind it is pretty simple. We are going to use default HTML Drag and Drop API that uses various drag and drop events. To make any HTML element draggable we just need to add draggable attribute to an element. Then we will maintain a state where will store data for all the dropped elements and then show them in the div. To achieve this will use various drag and drop even listeners already available with HTML5 and React.

🎯 The main trick is here is that we can not move an element/component literally. We will pass its data with it when we drag and then recreate the element/component again from the passed data inside the div we are dropping it in.

We will use events listeners like :

  1. OnDrop : This event activates when a valid draggable element is dropped in its area.
  2. OnDragStart : This is fired when dragging of an element is started.
  3. OnDragEnd : This is fired when dragging is ended.
  4. OnDragOver : This is fired when we drag over the area of an element

There are many more but for our use these are enough. So lets start the implementation.

The implementation πŸ§‘β€πŸ’»

Let's create some task elements in a div from where we will drag them from :

The task divs

and a todo div where we will drop them :

Image description

Now to make the task elements draggable we just add the draggable attribute.

<div
    className="..."
    draggable
>
    Task 1
</div>
Enter fullscreen mode Exit fullscreen mode

Now we can drag the task div. When we drag an element we need to pass its data with it so that we can recreate it. In this we case will pass its name.

To do this lets use onDragStart which fires when we start dragging an element.

<div
    className="..."
    draggable
    onDragStart={(e) => {
        handleOnDrag(e, "Task 1");
    }}
>
    Task 1
</div>
Enter fullscreen mode Exit fullscreen mode

Here we are passing the event e and the name of the task to the handleOnDrag function.

Lets write the handleOnDrag function

function handleOnDrag(e: React.DragEvent, name: string) {
    e.dataTransfer.setData("name", name);
}
Enter fullscreen mode Exit fullscreen mode

Here we are setting the name string as data under "name" key. Now whenever we drag it the name data will go along with it.

We handled the dragging part now lets drop it.

We need a state where we will store data for all the dropped tasks and later show them in the div where we dropped them.

const [tasks, setTasks] = useState<string[]>()
Enter fullscreen mode Exit fullscreen mode

Now we will use onDrop attribute to signal the dropping event. But before doing this we have to handle onDragOver for an edge case. Just do this :

<div 
    className="..."
    onDragOver={handleOnDragOver}
>
    {/* The div we are dropping the task in */}
</div>
Enter fullscreen mode Exit fullscreen mode

And now the handleOnDragOver function

function handleOnDragOver(e: React.DragEvent) {
    e.preventDefault();
}
Enter fullscreen mode Exit fullscreen mode

This will stop a default behavior where the onDragOver keeps firing idefinately.

Now for the onDrop

<div 
    className="..."
    onDragOver={handleOnDragOver}
    onDrop={handleOnDrop}
>
    {/* The div we are dropping the task in */}
</div>
Enter fullscreen mode Exit fullscreen mode

Now lets write the handleOnDrop function

function handleOnDrop(e: React.DragEvent) {
    if(tasks) {
        setTasks([...tasks,e.dataTransfer.getData("name")] )
    } else {
        setTasks([e.dataTransfer.getData("name")])
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we are creating a new array with the dropped task div's data and setting that array in the tasks state. The e.dataTransfer.getData("name") part will look familiar. Because we used e.dataTransfer.setData("name", name) in the handleOnDrag function to name the dragged task. And now we are getting that name data that we set previously when we started dragging it.

Now we have the data for the task that we are dropping. We now just have to show the task in the div using the data we got. We will use the same Task div we created earlier, inside the div we dropping it in. We will map through all the tasks in the tasks state and show them one by one.

<div 
    className="..."
    onDragOver={handleOnDragOver}
    onDrop={handleOnDrop}
>
    {tasks &&
        tasks.map((taskName) => {
            return (
                <div
                    className="..."
                    draggable
                    onDragStart={(e) => {
                        handleOnDrag(e, taskName);
                    }}
                >
                    {taskName}
                </div>
            );
        })}
</div>
Enter fullscreen mode Exit fullscreen mode

Finally we have the dragged task in the div we dropped it in.

Image description

But there is a catch. The Task is getting duplicated every time we drag and drop it the task again in the div.

Image description

To solve we have to delete the previous instance of the task in the state when we move it. Its pretty easy to do with filter()

function handleOnDrop(e: React.DragEvent) {
    if(tasks) {
        setTasks([
            ...tasks.filter(
                (taskName) => taskName !== e.dataTransfer.getData("name")
            ),
            e.dataTransfer.getData("name"),
        ]);
    } else {
        setTasks([e.dataTransfer.getData("name")])
    }
}
Enter fullscreen mode Exit fullscreen mode

Now everything is working. We can drag tasks from the list and drop them in the Todo div.

Image description

How to extend the idea further ➑️

The basic stuff is set up. Now we can make it more user friendly by giving visual feedback when we start dragging the task and when we drop it. For that we can use onDragStart, onDragEnd, onDragOver etc. events to change the CSS styles of the elements. We can also add animations.

In part 2, we we will do all of these and also add two more columns for ongoing and completed tasks and make a fully functioning Kanban board-like todo board. We will need to manage three separate states and many more complexities. Part 2 coming soon βœ…

Part 2 is releasedπŸ”₯

Check it out here

Link to full code : Github Link

I hope this was helpful to you 😊
Follow me on Twitter and LinkedIn
Thanks for reading πŸ˜‡

Top comments (17)

Collapse
 
jorensm profile image
JorensM • Edited

Great article, thanks! I'm wondering though what is the current browser support for this API? The MDN docs don't say anything abou that.

Edit: it looks like according to caniuse.com, the support is mostly good except on some less popular mobile browsers.

Collapse
 
nasif2ahmed profile image
Ahmed

All up to date modern desktop browsers I tested supports it.
But I found than in mobile browsers, it doesn't work as expected with the same code. I will have to look into it. Thanks for the heads up.

Collapse
 
samfelgar profile image
Samuel Felipe Gacia

Great article! I followed it but using Vue. Here's the repo if anyone is interested: repo.

Collapse
 
nasif2ahmed profile image
Ahmed

Excellent work friend πŸ‘

Collapse
 
rojansr profile image
Rojan Rai

Great stuff, did the same way back some months ago but then found that it doesn't work on mobile devices. So have to rely on other js libraries.

Collapse
 
nasif2ahmed profile image
Ahmed

Yes I faced problem in mobile devices too. I think there is some way to fix it. I am working on it.
Other js libraries too had to manually implement it didn't they ? 😊

Collapse
 
random_ti profile image
Random • Edited

Great Article Hassan, But if you show live demo with code-pen embedding it would be more helpful.

Collapse
 
nasif2ahmed profile image
Ahmed

Thanks for the suggestion. I will try to do that from next time. πŸ™‚

Collapse
 
rittschapp profile image
Philip Rittscher

Great article. One minor note that your function name was missing "On"

function handleDragOver(e: React.DragEvent)

Collapse
 
nasif2ahmed profile image
Ahmed

Thanks for pointing it out :D

Collapse
 
kansoldev profile image
Yahaya Oyinkansola

Nice article Ahmed, it was a good read. I want to ask, how did you make a gif video in this your article?, I am trying to do the same, but don't know how

Collapse
 
nasif2ahmed profile image
Ahmed • Edited

Thanks. I just recorded part of the screen with windows' snipping tool and then converted that video to gif using this
Hope it helps :)

Collapse
 
kansoldev profile image
Yahaya Oyinkansola

Okay thank you very much

Collapse
 
vechet profile image
Chuo Vechet

Good sharing.

Collapse
 
devalienbrain profile image
HASSAN - Alien Brain

Great article ...

Collapse
 
nasif2ahmed profile image
Ahmed

Thank you

Collapse
 
ngud0119 profile image
NGUD-0119: ACE

Awesome