DEV Community

Cover image for Drag and Drop Kanban board from scratch with React : Part 2 🚀
Ahmed
Ahmed

Posted on

Drag and Drop Kanban board from scratch with React : Part 2 🚀

In part 1 we discussed the basic idea of how to implement the drag and drop feature. In this part we will extend the idea and make the full Kanban board. Go read part 1 first if you haven't.

We are going to add two more columns for the kanban board. One for ongoing tasks and one for completed tasks

Multiple States

Previously we only needed to maintain only one state tasks for todo list. But if we want to add two more columns or container for tasks to drop in then we need two more states. In total three separate states.

const [todo, setTodo] = useState<string[]>();
const [ongoing, setOngoing] = useState<string[]>();
const [completed, setCompleted] = useState<string[]>();
Enter fullscreen mode Exit fullscreen mode

So basically when we drop a task in Todo column it would get added to todo state and get deleted from previous state it was in and vice versa.

Add two more columns

Lets now create ongoing and completed column alongside todo column. Which is very easy to do, we just have to copy paste the todo column's code and rename it. We should also replace old generic handleOnDrop function with their own functions.

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

and

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

You will notice we also changed the state name according to the states we created earlier and a new onDrop function for each column. So we also applied the changes to our old todo div

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

Now we have all the columns.

Image description

onDrop handlers

Removing the old handleOnDrop function, we now have three onDrop functions instead of one : handleOnDropTodo, handleOnDropOngoing and handleOnDropCompleted

Now basically, there can be 3*2 = 6 scenarios. For example,

  • Dropping in todo list from ongoing
  • Dropping in ongoing list from todo
  • Dropping in todo list from completed
  • Dropping in completed list from todo ...... etc.

You get the idea.

Lets start with handleOnDropTodo function. Setting the task in the dropped column's state is same as before. New part is to delete the old instance from ongoing state if it came from ongoing or from completed state if it came from completed column.

The function would look like this :

function handleOnDropTodo(e: React.DragEvent) {

    // Set the dropped task to todo state
    if (todo) {
        setTodo([
            ...todo.filter(
                (taskName) => taskName !== e.dataTransfer.getData("name")
            ),
            e.dataTransfer.getData("name"),
        ]);
    } else {
        setTodo([e.dataTransfer.getData("name")]);
    }

    // If dropping from ongoing --> todo
    // Delete from ongoing
    ongoing?.forEach((task) => {
        if (task === e.dataTransfer.getData("name")) {
            setOngoing([
                ...ongoing.filter(
                    (taskName) =>
                        taskName !== e.dataTransfer.getData("name")
                ),
            ]);
        }
    });

    // If dropping from completed --> todo
    // Delete from completed
    completed?.forEach((task) => {
        if (task === e.dataTransfer.getData("name")) {
            setCompleted([
                ...completed.filter(
                    (taskName) =>
                        taskName !== e.dataTransfer.getData("name")
                ),
            ]);
        }
    });
}

Enter fullscreen mode Exit fullscreen mode

Now we have to write handleOnDropOngoing function. Which is pretty easy to do by copy pasting the code for handleOnDropTodo and changing the names.

function handleOnDropOngoing(e: React.DragEvent) {
    // Set the dropped task to todo state
    if (ongoing) {
        setOngoing([
            ...ongoing.filter(
                (taskName) => taskName !== e.dataTransfer.getData("name")
            ),
            e.dataTransfer.getData("name"),
        ]);
    } else {
        setOngoing([e.dataTransfer.getData("name")]);
    }

    // If dropping from todo --> ongoing
    // Delete from todo
    todo?.forEach((task) => {
        if (task === e.dataTransfer.getData("name")) {
            setTodo([
                ...todo.filter(
                    (taskName) =>
                        taskName !== e.dataTransfer.getData("name")
                ),
            ]);
        }
    });

    // If dropping from completed --> ongoing
    // Delete from completed
    completed?.forEach((task) => {
        if (task === e.dataTransfer.getData("name")) {
            setCompleted([
                ...completed.filter(
                    (taskName) =>
                        taskName !== e.dataTransfer.getData("name")
                ),
            ]);
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

Lastly the handleOnCompleted function is easy to write following the last two, as we can see. So I am skipping it here.

We have all the states, column divs and onDrop handlers ready. Things should start working perfectly now.

Image description

Improvements

The code can be improved quite a lot. To keep things simple I wrote everything in one file and repeated codes.

  • We can make a separate todo component with more info like deadline, priority, description etc.
  • We can make hooks to handle the various dropping and deleting actions for states.
  • Instead of already make task we can add a form to dynamically create tasks.
  • Lastly, make all task data persistent by using localstorage or a database.

I already did all of these in a website I made using the same logic as this article called Task Master :

Task Master Website

You can check the code for Task Master here : Github Link

Thank You

Link to the article's full code : Github Link

I hope this was helpful to you 😊
Follow me on Twitter and LinkedIn
Thanks for reading 😇

Top comments (0)