learn react ui logoLearnReactUI
KanbanBoard React Example

Kanban Board Example

This project implements a Kanban Board system with core features such as drag-and-drop functionality, task editing, and customizable columns. It offers an intuitive interface for managing tasks and workflows efficiently, enabling seamless updates and organization of tasks across multiple columns.

[Demo]

1. What is a Kanban Board?

A Kanban Board is a visual tool used to plan, track and manage a job. It is usually organized in columns and cards on a board.

Columns (Columns): Represents the stages of the workflow (e.g. “To Do”, “In Progress”, “Completed”).

Cards (Tasks): Represents the work to be done and usually each card refers to a task or piece of work.

Objective:

  • Visualize work processes, providing quick information about the status of tasks.
  • Improves communication and collaboration between team members.
  • Helps to identify bottlenecks in processes and opportunities for improvement.

Areas of Use: It can be applied in many areas such as software development, project management, production processes and personal task management.

2. Kanban Board Development using KnowledgeMap

In general, these information maps are generated as we go. I began with a simple example for Kanban Board Development.

Note: In the future, you may adjust server aspects like connection with the server, optimistic update or local-first, react server components, or make stylistic enhancements.

Kanban Board

Basic: A simple example of assigning jobs from one location to another using three columns.

Dnd: An example of how to shift jobs from one area to another using drag and drop

Add Task: An example of how to add a new task, update its content, and delete it.

Modify Columns: This example shows how to add, remove, or rename a new column.significant

Note: Because the project codes for the examples below will be available for download at LearnReactUI.dev, I will simply highlight and discuss the most significant sections of the code.

3. Examples

3.1. Basic

I aimed to make the first example as simple and straightforward as possible. There are three columns and three tasks. We shall indicate which column these tasks are in based on their state.

Kanban Board Basic

As you can see below, we indicate these tasks and their status.

  tasks: [
    {id: 1, title: 'Task 1', status: 'To Do'},
    {id: 2, title: 'Task 2', status: 'In Progress'},
    {id: 3, title: 'Task 3', status: 'Done'},
  ],

After that, we are making the infrastructure to keep these tasks in the Zustand Store and update their status.

const useKanbanStore = create((set) => ({
  tasks: [
    { id: 1, title: "Task 1", status: "To Do" },
    { id: 2, title: "Task 2", status: "In Progress" },
    { id: 3, title: "Task 3", status: "Done" },
  ],
  updateTaskStatus: (id, newStatus) =>
    set((state) => ({
      tasks: state.tasks.map((task) =>
        task.id === id ? { ...task, status: newStatus } : task
      ),
    })),
}));

Then we need to create these Task components. We write the KanbanTask component for these. Here, when the option of the task changes, we just need to update the Store (updateTaskStatus)

const KanbanTask = ({ task }) => {
  const { updateTaskStatus } = useKanbanStore();

  const handleChange = (newStatus) => {
    updateTaskStatus(task.id, newStatus);
  };

  return (
    <Card
      style={{ marginBottom: "10px" }}
      title={task.title}
      extra={
        <Select
          defaultValue={task.status}
          style={{ paddingLeft: 8, width: 120 }}
          onChange={handleChange}
        >
          <Select.Option value="To Do">To Do</Select.Option>
          <Select.Option value="In Progress">In Progress</Select.Option>
          <Select.Option value="Done">Done</Select.Option>
        </Select>
      }
    >
      Task details go here.
    </Card>
  );
};

After that we write the column component. KanbanColumn. This basically holds the components and is responsible for rendering them.

const KanbanColumn = ({ title, tasks }) => {
  return (
    <Styled.KanbanColumn>
      <Typography.Title level={4}>{title}</Typography.Title>
      {tasks.map((task) => (
        <KanbanTask key={task.id} task={task} />
      ))}
    </Styled.KanbanColumn>
  );
};

Finally, to write the part that knows KanbanBoard, that is, the part that renders the tasks by associating the relevant column.

const KanbanBoard = () => {
  const { tasks } = useKanbanStore();
  const columns = ["To Do", "In Progress", "Done"];

  return (
    <Row style={{ height: "100vh" }}>
      {columns.map((column) => (
        <Col
          key={column}
          span={8}
          style={{
            borderRight: "1px solid #ccc",
            padding: "10px",
          }}
        >
          <KanbanColumn
            title={column}
            tasks={tasks.filter((task) => task.status === column)}
          />
        </Col>
      ))}
    </Row>
  );
};

3.2. Dnd (Drag and Drop)

The second example is Dnd (Drag and Drop), which allows moving the tasks on the Kanban Board from one column to another. So we want to simplify and increase usability.

Kanban Board Dnd

The first thing we need to do is to surround the KanbanBoard component with DndProvider, this will allow all our internal components to use Drag and Drop capabilities. (For detailed information about the Provider Pattern, you can read the document at this link)

<DndProvider backend={HTML5Backend}>
  <Styled.KanbanBoard>
    <KanbanBoard />
  </Styled.KanbanBoard>
</DndProvider>

Secondarily, we will make the columns able to detect drop. We add drop capability to KanbanColumn component with useDrop.

const KanbanColumn = ({ title, tasks }) => {
  const { updateTaskStatus } = useKanbanStore();

  const [, drop] = useDrop({
    accept: "task",
    drop: (item) => updateTaskStatus(item.id, title),
  });

  return (
    <Styled.KanbanColumn ref={drop}>
      <Typography.Title level={4}>{title}</Typography.Title>
      {tasks.map((task) => (
        <KanbanTask key={task.id} task={task} />
      ))}
    </Styled.KanbanColumn>
  );
};

Thirdly, we need to make Tasks portable. We add a draggable feature to the KanbanTask component with useDragging

const KanbanTask = ({ task }) => {
  const [{ isDragging }, drag] = useDrag(() => ({
    type: "task",
    item: { id: task.id },
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  }));

  return (
    <Card
      ref={drag}
      style={{
        marginBottom: "10px",
        opacity: isDragging ? 0.5 : 1,
        cursor: "move",
      }}
    >
      {task.title}
    </Card>
  );
};

We can now manage the status of tasks by drag and drop between columns.

3.3. Add Task (Create New Task)

In the third example, I want to improve our application a bit more and add the ability to add, delete and update new tasks.

Kanban Board Dnd

The first thing we will do is add new functions related to Zustand Store Task, addTask, deleteTask, updateTask

// Add a new task
  addTask: (title, status) =>
    set((state) => ({
      tasks: [...state.tasks, {id: Date.now().toString(), title, status}],
    })),

  // Delete a task by ID
  deleteTask: (id) =>
    set((state) => ({
      tasks: state.tasks.filter((task) => task.id !== id),
    })),

  // Update task title by ID
  updateTask: (id, newTitle) =>
    set((state) => ({
      tasks: state.tasks.map((task) => (task.id === id ? {...task, title: newTitle} : task)),
    })),

Then we call these Zustand store functions in the background via the buttons and the Modal screens opened from them.

3.4. Modify Columns

In our 4th and last example, we want to update, delete and add new columns

Here, the first thing we need to do is update the ZustandStore, addColumn, modifyColumn and deleteColumn functions.

// Add a new column
  addColumn: (columnName) =>
    set((state) => ({
      columns: [...state.columns, columnName],
    })),

  // Rename a column
  modifyColumn: (oldName, newName) =>
    set((state) => ({
      columns: state.columns.map((col) => (col === oldName ? newName : col)),
      tasks: state.tasks.map((task) => (task.status === oldName ? {...task, status: newName} : task)),
    })),

  // Delete a column and its tasks
  deleteColumn: (columnName) =>
    set((state) => ({
      columns: state.columns.filter((col) => col !== columnName),
      tasks: state.tasks.filter((task) => task.status !== columnName),
    })),

It remains to make button and modal screens that will call the functions in this store and trigger these functions from this section.