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[]>();
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>
and
<div
className="..."
onDragOver={handleOnDragOver}
onDrop={handleOnDropCompleted}
>
{completed &&
completed.map((taskName) => {
return (
<div
className="..."
draggable
onDragStart={(e) => {
handleOnDrag(e, taskName);
}}
>
{taskName}
</div>
);
})}
</div>
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>
Now we have all the columns.
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")
),
]);
}
});
}
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")
),
]);
}
});
}
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.
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 :
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)