React Kanban Board

In this tutorial we will learn how to setup a very simple Kanban Board with React. The board will be interactive, meaning that we can drag cards between columns.

Introduction

In this tutorial we will learn how to setup a very simple Kanban Board with React. The board will be interactive, meaning that we can drag cards between columns. We will make use of the react-kanban library, which we can install with the following NPM command:

npm install @lourenci/react-kanban

The Kanban Board we are going to import in the code below can be used both in a controlled and uncontrolled way [1]. For this tutorial, we will use the controlled approach, which means that we need to keep the board state in our application (on the state, in our simple example) and pass that state to the component [1]. Although this adds some more complexity to the implementation, it also brings more control. Furthermore, the module makes available some helpers to deal with the board state shape [1].

If you want to play around with an example board before analyzing the code, you can check this example from the library.

My environment was setup using Create React App.

A simple example

As usual, we will start our code by the import statements. We will need ReactReactDom and the useState Hook, to write our component.

import React, { useState } from "react";
import ReactDOM from "react-dom";

Then we will import the Board component and the moveCard helper function. Since, as already mentioned, we are going to use the Board as a controlled component, we will make use of the helper functions to manage the state more easily.

import Board, { moveCard } from "@lourenci/react-kanban";

We will also make use of the library default styles, which means we need to import it in our code.

import "@lourenci/react-kanban/dist/styles.css";

After this, we will define a variable containing the initial state of the board. we will call this variable initialBoard. You can check here the shape of the board.

As can be seen on the previous link, the state of the board is an object that contains an array of columns. Each column has a unique identifier, a title and an array of cards. Then, each card has also a unique identifier, a title and, additionally, a description.

For exemplification purposes, we will create two columns: one with To-Dos and another with the items Done. So, we will be able to drag items between those. We will start with two cards in the first column (some to-dos) and one card already done.

const initialBoard = {
  columns: [
    {
      id: 1,
      title: "To-Do",
      cards: [
        {
          id: 1,
          title: "Write post",
          description: "Write a new post for blog."
        },
        {
          id: 2,
          title: "Cook dinner",
          description: "Cook an awesome dinner."
        }
      ]
    },
    {
      id: 2,
      title: "Done",
      cards: [
        {
          id: 3,
          title: "Fix car",
          description: "Fix my car problem"
        }
      ]
    }
  ]
};

Then we will define a wrapper component. This component will hold the board state and render the Board imported from the module we installed.

function MyBoard() {
  // Wrapper component implementation
}

The first thing we will do is declaring a new state variable. This variable will hold the kanban board state. The variable will be called board and the update function will be called setBoard. The initial state will be the initialBoard variable we declared before.

const [board, setBoard] = useState(initialBoard);

Then we will declare a function that will be used as callback for whenever a card is dragged to a different position (either on the same column or to a different one).

This function receives as input the moved card, the source position and the destination position. The 3 parameters are objects:

  • card contains the id, the title and the description of the moved card;
  • source contains the source column identifier and the source position on that column (the position is zero based);
  • destination contains the destination column identifier and the destination position on that column (also zero based).
function handleCardMove(card, source, destination) {
   // Callback function implementation
}

To get the updated board state based on these callback parameters, we will make us of the moveCard helper function. This function receives as first input the current board state (board variable), the source object and the destination object. As output, it returns the new board state, considering the card movement. This can be used to directly set the new component state.

const updatedBoard = moveCard(board, source, destination);
setBoard(updatedBoard);

The complete callback function can be seen below. We have added the prints of the 3 parameters and the new board state.

function onCardMove(card, source, destination) {
    const updatedBoard = moveCard(board, source, destination);
    setBoard(updatedBoard);
    
    console.log("----------");
    console.log(card);
    console.log(source);
    console.log(destination);
    console.log(updatedBoard);
}

To finalize, we will return the Board component after setting some of its props. We will set the onCardDragEnd prop to our onCardMove callback function, to ensure the state is updated upon user interaction with the board. We will also set the disableColumnDrag to true to avoid the user to be able to drag the columns.

The board state should be passed as children of the Board component.

return (
    <Board 
      onCardDragEnd={onCardMove} 
      disableColumnDrag
    >
      {board}
    </Board>
);

The complete component can be seen below.

function MyBoard() {

  const [board, setBoard] = useState(initialBoard);

  function onCardMove(card, source, destination) {
    const updatedBoard = moveCard(board, source, destination);
    setBoard(updatedBoard);
    
    console.log("----------");
    console.log(card);
    console.log(source);
    console.log(destination);
    console.log(updatedBoard);
  }

  return (
    <Board 
      onCardDragEnd={onCardMove} 
      disableColumnDrag
    >
      {board}
    </Board>
  );
}

To finalize our app, we will wrap the MyBoard component in a call to the render method of the ReactDOM object. I’m assuming the HTML file that will contain the app has an element called “root“.

const rootElement = document.getElementById("root");
ReactDOM.render(<MyBoard />, rootElement);

The complete code can be seen below.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import Board, { moveCard } from "@lourenci/react-kanban";
import "@lourenci/react-kanban/dist/styles.css";

const initialBoard = {
  columns: [
    {
      id: 1,
      title: "To-Do",
      cards: [
        {
          id: 1,
          title: "Write post",
          description: "Write a new post for blog."
        },
        {
          id: 2,
          title: "Cook dinner",
          description: "Cook an awesome dinner."
        }
      ]
    },
    {
      id: 2,
      title: "Done",
      cards: [
        {
          id: 3,
          title: "Fix car",
          description: "Fix my car problem"
        }
      ]
    }
  ]
};

function MyBoard() {

  const [board, setBoard] = useState(initialBoard);

  function onCardMove(card, source, destination) {
    const updatedBoard = moveCard(board, source, destination);
    setBoard(updatedBoard);
    
    console.log("----------");
    console.log(card);
    console.log(source);
    console.log(destination);
    console.log(updatedBoard);
  }

  return (
    <Board 
      onCardDragEnd={onCardMove} 
      disableColumnDrag
    >
      {board}
    </Board>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<MyBoard />, rootElement);

To test the code, simply compile and serve the application. You should get a result similar to figure 1. As can be seen, we get a kanban board with the two columns we configured, and the cards placed as we set for the initial state.

React Kanban board rendered on the browser.
Figure 1 – React kanban board rendered on the browser.

If we then drag one of the cards from the first column to the second, we should obtain the result shown in figure 2 on the browser console. As can be seen, we have access to the card that was dragged and the source and destination positions. We also get the new state, given this transition.

 Output of the card position change callback.
Figure 2 – Output of the card position change callback.

Figure 3 shows a small demo of dragging and dropping the cards between columns.

Interaction with the board.
Figure 3 – Interaction with the board.

References

[1] https://github.com/lourenci/react-kanban

Leave a Reply

%d bloggers like this: