Building a Kanban style todo app
Tags: react, html, javascript, css
Published: 2022-07-12 13:15:54

---
title: Building a Kanban style todo app
published: true
description: Hello fellow humanoids. In this post we will build a Kanban style todo app
tags: React, html, javascript, css
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7nr9ul4l63t5ogviedi.png
---

## What is this post about ?
In this article we will explore how to build a Kanban style todo app. Each todo task will have 3 possible states : Not started, In Progress, Done. As soon as a new todo is added the default state would be Not started and the user will be able to drag between states. This app is not focused on styling rather on the bare minimum functional implementation.

Check out the app here : [Kanban style todo App](https://draggable-todo-app.netlify.app/)

![Kanban style todo app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l1bwixkcmhsbha5761c.png)

## Content
* Design
* Add new todo
* Changing todo state
* Deleting todo

Lets go deep dive into each one and explore how it was implemented.

## Design
![Design](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3qgwcigo7uymc315txwf.png)

## Add new todo
Todo will be added using Input area and the default state will be Not Started.

```
// Component
const TodoInput = ({ onTodoAdd }) => {
  const [todoInput, setTodoInput] = useState("");
  const onAdd = (e) => {
    e.preventDefault();
    onTodoAdd(todoInput);
    setTodoInput("");
  };
  return (
    <form className="todo-input">
      <input
        placeholder="Add Todo entry"
        value={todoInput}
        onChange={(e) => setTodoInput(e.target.value)}
      />
      <button onClick={onAdd} type="submit">
        Add
      </button>
    </form>
  );
};

// onTodoAdd Prop implementation
const onTodoAdd = (todoText) => {
    setTodos((_t) => {
      return [
        ..._t,
        {
          id: uuidv4(), // Generate unique id 
          value: todoText,
          state: TODO_STATE.NOT_STARTED,
        },
      ];
    });
  };
```


## Changing todo state
Depending on the states for the todo we will need that many state containers to hold the todo. So in this case there will be 3 containers [Not started, In Progress, Done].
Each container will act as drop zone for the items to be dropped on. 
Each task item will act as draggable item and can be dropped in any of the drop zones available. 
Drop zones : Not started , In Progress, Done, Delete.
For implementing drag and drop functionality we will be using [React DnD](https://react-dnd.github.io/react-dnd/about)

```
export const TODO_STATE = {
  NOT_STARTED: "Not started",
  IN_PROGRESS: "In progress",
  DONE: "Done",
};

const TodoContent = ({ todos, onTodoDrag, onTodoDelete }) => {
  const notStartedTodos = getTodosBasedOnState(todos, TODO_STATE.NOT_STARTED);
  const inProgressTodos = getTodosBasedOnState(todos, TODO_STATE.IN_PROGRESS);
  const doneTodos = getTodosBasedOnState(todos, TODO_STATE.DONE);
  const [isDragActive, setIsDragActive] = useState(false);
  const onDragActive = (dragActive) => {
    setIsDragActive(dragActive);
  };
  return (
    <DndProvider backend={HTML5Backend}>
      <div className="todo-content">
        <DraggableItemContainer
          title="Not Started"
          todos={notStartedTodos}
          onTodoDrag={onTodoDrag}
          state={TODO_STATE.NOT_STARTED}
          onDragActive={onDragActive}
          onTodoDelete={onTodoDelete}
        />
        <DraggableItemContainer
          title="In Progress"
          todos={inProgressTodos}
          onTodoDrag={onTodoDrag}
          state={TODO_STATE.IN_PROGRESS}
          onDragActive={onDragActive}
          onTodoDelete={onTodoDelete}
        />
        <DraggableItemContainer
          title="Done"
          todos={doneTodos}
          onTodoDrag={onTodoDrag}
          state={TODO_STATE.DONE}
          onDragActive={onDragActive}
          onTodoDelete={onTodoDelete}
        />
      </div>
      {isDragActive && (
        <div className="delete-drag-container">
          <DeleteDragItemBox />
        </div>
      )}
    </DndProvider>
  );
};
```
Draggable Container 
```
const DraggableItemContainer = ({
  title = "",
  todos = [],
  onTodoDrag,
  state,
  onDragActive,
  onTodoDelete,
}) => {
  const [{ canDrop, isOver }, drop] = useDrop(() => ({
    accept: ITEM_TYPES.TODO,
    drop: () => ({ state }),
    collect: (monitor) => ({
      isOver: monitor.isOver(),
      canDrop: monitor.canDrop(),
    }),
  }));
  const isActive = canDrop && isOver;
  const style = {
    border: isActive ? "3px dashed black" : "1px solid black",
  };
  return (
    <div className="draggable-item-container" ref={drop} style={style}>
      <h4 className="title">
        {title} - [{todos.length}]
      </h4>
      <div className="content">
        {todos.map((t) => {
          return (
            <DraggableItem
              key={t.id}
              todo={t}
              onTodoDrag={onTodoDrag}
              onDragActive={onDragActive}
              onTodoDelete={onTodoDelete}
            />
          );
        })}
      </div>
    </div>
  );
};
```
Draggable Item a.k.a Todo item 
```
const DraggableItem = ({ todo, onTodoDrag, onDragActive, onTodoDelete }) => {
  const [{ isDragging }, drag] = useDrag(() => ({
    type: ITEM_TYPES.TODO,
    item: { todo },
    end: (item, monitor) => {
      const dropResult = monitor.getDropResult();
      if (item && dropResult) {
        if (!dropResult.delete) {
          onTodoDrag(item.todo.id, dropResult.state);
        } else {
          onTodoDelete(item.todo.id);
        }
        onDragActive(false);
      }
    },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
      handlerId: monitor.getHandlerId(),
    }),
    isDragging: (monitor) => {
      if (todo.id === monitor.getItem().todo.id) {
        onDragActive(true);
      }
    },
  }));
  const opacity = isDragging ? 0.4 : 1;
  const textDecoration =
    todo.state === TODO_STATE.DONE ? "line-through" : "none";
  let backgroundColor = "";
  switch (todo.state) {
    case TODO_STATE.NOT_STARTED: {
      backgroundColor = "lightcoral";
      break;
    }
    case TODO_STATE.IN_PROGRESS: {
      backgroundColor = "lightyellow";
      break;
    }
    case TODO_STATE.DONE: {
      backgroundColor = "lightgreen";
      break;
    }
    default: {
      backgroundColor = "white";
      break;
    }
  }
  return (
    <div
      className="draggable-item"
      ref={drag}
      style={{ opacity, textDecoration, backgroundColor }}
    >
      {todo.value}
    </div>
  );
};
```
Event : onTodoDrag
```
const onTodoDrag = (id, state) => {
    setTodos((_t) =>
      _t.map((t) => {
        if (t.id === id) {
          return { ...t, state };
        }
        return t;
      })
    );
 };
```

## Deleting todo
Delete Drop zone 
```
const DeleteDragItemBox = () => {
  const [{ canDrop, isOver }, drop] = useDrop(() => ({
    accept: ITEM_TYPES.TODO,
    drop: () => ({ delete: true }),
    collect: (monitor) => ({
      isOver: monitor.isOver(),
      canDrop: monitor.canDrop(),
    }),
  }));
  const isActive = canDrop && isOver;
  const style = {
    border: isActive ? "3px dashed black" : "none",
  };
  return (
    <div className="delete-drag-box" style={style} ref={drop}>
      <DeleteIcon width={'4rem'}/>
    </div>
  );
};

const onTodoDelete = (id) => {
    setTodos((_t) => _t.filter((t) => t.id !== id));
};
```

## Conclusion
Implementing this game will allow you to learn about using state and side effects in React to implement the desired logic. This app was made as part of learning new components which are used in real life applications. 
Stay safe and lend a hand to another :) 

* [Kanban style todo App](https://draggable-todo-app.netlify.app/)
* [Project Source](https://github.com/vigneshiyergithub/draggable-todo-app)
* [Vignesh Iyer](https://vigneshiyer.netlify.app/)