Codementor Events

Developing a Kanban Board using react-dnd

Published Jan 01, 2019Last updated Jun 29, 2019


Kanban Board

Drag-and-Drop is one of the cool HTML5 features. Previously we relied on javascript mouse events to achieve drag-and-drop. But HTML5 simplified the work flow. But making drag-and-drop to work in React environment is difficult. Because, there may be conflict between direct DOM manipulation and the virtual DOM maintained by React. This is where react-dnd comes into picture, which is a simple, straight forward library that helps us to work with HTML5 drag-and-drop in the react environment.

The code used in this tutorial is available at https://gist.github.com/rethna2/c89a6e30dfc96296a1ddb6e1911a6bda

Also you can check it out in the below codesandbox.

To begin with, we need to add two libraries.

$ npm install react-dnd react-dnd-html5-backend

$ npm install react-dnd react-dnd-html5-backend
  1. react-dnd provides all the logic and
  2. react-dnd-html5-backend binds the logic to the html5 api.

There are three dom nodes or jsx component to handle to complete the work flow.

  1. draggable objects : In our example of Kanban board, Every task item is a draggable object. Items that we can drag around.
  2. dropable containers: Each column in a kanban board is a dropable container. We can drag any of the task item and drop them in this container
  3. Drag and Drop Context: A wrapper which encloses all the draggable objects and dropable containers. This is needed to set the boundary and initialize the stage. It is possible to have multiple drag-and-drop, by having multiple non overlapping context.

All these three features are implemented as Higher order components wrapping around the view components.

import { DragDropContext, DropTarget, DragSource } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";

The board is represented by the below structure. It may look a little complex but let me highlight some points.

<section>
{channels.map(channel => (
  <KanbanColumn status={channel}>
    <div>
      <div>{labelsMap[channel]}</div>
      <div>
      {tasks.filter(item => item.status === channel)
        .map(item => (
          <KanbanItem id={item._id} onDrop={this.update}>
            <div style={classes.item}>{item.title}</div>
          </KanbanItem>
      ))}
      </div>
    </div>
  </KanbanColumn>
  ))}
</section>

We have a two level nested dom loop using Array.map, the first one renders the columns of the kanban board and the second one renders the task items. Also notice there are two components which I will explain sooner. But before that the root kanban component should be wired with react-dnd like below.

export default DragDropContext(HTML5Backend)(Kanban);

Dropable Container

The kanban board column component is represented as below.

const boxTarget = {
  drop(props) {
    return { name: props.status };
  }
};

class KanbanColumn extends React.Component {
  render() {
    return this.props.connectDropTarget(
      <div>{this.props.children}</div>
    );
  }
}

KanbanColumn = DropTarget("kanbanItem", boxTarget, 
  (connect, monitor) => ({
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    canDrop: monitor.canDrop()
  }))(KanbanColumn);

boxTarget object has the collection of events we are interested in. Here we are only interested in the ‘drop’ event. Once a drop event happens, we are telling that the task item is dropped on the particular column which is available at ‘props.status’

KanbanColumn component doesn’t contain the UI, instead it is passed down as children.

Draggable Item

const boxSource = {
  beginDrag(props) {
    return {
      name: props.id
    };
  },
  endDrag(props, monitor) {
    const item = monitor.getItem();
    const dropResult = monitor.getDropResult();
    if (dropResult) {
      props.onDrop(monitor.getItem().name, dropResult.name);
    }
  }
};

class KanbanItem extends React.Component {
  render() {
    return this.props.connectDragSource(
      <div>{this.props.children}</div>
    );
  }
}

KanbanItem = DragSource("kanbanItem", boxSource, 
  (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging()
  }))(KanbanItem);

This is similar to the previous component, but it has something extra.

The boxSource object is the collection of listeners we are interested in. Actually we are interested in two events. When the drag begins, we collect the id of the item being dragged. And when the drop completes, we collect the name of the column on which it is being dropped.

Whenever a drag and drop happens we are interested in two information. What is being dragged? And where the item is dropped? Once we have both we pass it to the parent component ( props.onDrop )

And it is responsibility of the parent to handle the state change. In our example we use ‘immutability-helper’ library. But there are many ways to handle this.

And a final note. This works fine only on computers and not on touch screens, because html5 drag-and-drop is not natively available in touch devices. But there are other backend alternatives which plays fine with react-dnd.

Discover and read more posts from Rethna
get started
post commentsBe the first to share your opinion
alex hood
5 years ago
alex hood
5 years ago

Written very well, Really like your blog.

https://notepadformac.co
https://panda-helper.co

iosman
5 years ago

love your blog.

Show more replies