React js drag and drop todo list
Tags: webdev, javascript, react
Published: 2023-05-13 18:19:07

In this blog I'll be showing how to build a draggable todo list with React beautiful dnd. If you're not familiar with React beautiful dnd, it's an amazing library to implement drag and drop functionality in your React application. In this step by step tutorial we'll be building a simple todo list with the ability to reorder items and drag and drop items to another column. Let's start by setting up a basic React app and the necessary components from React beautiful dnd.

### Installation
Let us first install our react app with [vite](https://vitejs.dev/guide/), then install [tailwind css framework](https://tailwindcss.com/docs/guides/vite).
Next install [react-beautiful-dnd](https://www.npmjs.com/package/react-beautiful-dnd) for our drag and drop functionality.

### Components
Let's walk through our components.
Create a `droppable` component to work with React Strict mode enabled.
[details](https://medium.com/@wbern/getting-react-18s-strict-mode-to-work-with-react-beautiful-dnd-47bc909348e4?source=post_internal_links---------0----------------------------)

#####Droppable Component

```javascript
import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";

const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
  const [enabled, setEnabled] = useState(false);

  useEffect(() => {
    const animation = requestAnimationFrame(() => setEnabled(true));

    return () => {
      cancelAnimationFrame(animation);
      setEnabled(false);
    };
  }, []);

  if (!enabled) {
    return null;
  }

  return <Droppable {...props}>{children}</Droppable>;
};

export default StrictModeDroppable;
```
The `Droppable component` defines a drop zone for draggable items.
[Learn more about Droppable](https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md)

#####Draggable Card

```javascript
import { Draggable } from "react-beautiful-dnd";

interface CardProps {
  id: number;
  title: string;
  draggableId: string;
  index: number;
}
const Card = ({ title, draggableId, index }: CardProps) => {
  return (
    <Draggable draggableId={draggableId} index={index}>
      {(provided, snapshot) => (
        <div
          ref={provided.innerRef}
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          className={`${
            snapshot.isDragging ? "bg-gray-100" : "bg-white"
          } px-2 py-4 font-medium w-full h-24 shadow-md shadow-blue-300 rounded-md`}
        >
          {title}
        </div>
      )}
    </Draggable>
  );
};

export default Card;

```
We have a card component to show each task for our todo list which is wrapped in a `Draggable component` which is used to define a draggable item in a drag-and-drop context. It requires a draggableId and index to be passed as props. [Learn more about Draggable](https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/draggable.md)

#####Column Component

```javascript
import Card from "./card";

interface ItemsColumnProps {
  columnTitle: string;
  items: { id: number; title: string }[];
}

const ItemsColumn = ({ columnTitle, items }: ItemsColumnProps) => {
  return (
    <div
      className="h-[392px] scrollbar-thin scrollbar-thumb-blue-700 
    scrollbar-track-blue-300 overflow-y-auto
      p-4 rounded-md border border-blue-300"
    >
      <p className="inline-block py-1 px-2 text-lg font-semibold bg-blue-300 rounded-md">
        {columnTitle}
      </p>
      <div className=" pt-4 flex flex-col gap-y-3">
        {items &&
          items.length > 0 &&
          items.map((item, index) => (
            <Card
              key={item.id}
              draggableId={item.id.toString()}
              index={index}
              id={item.id}
              title={item.title}
            />
          ))}
      </div>
    </div>
  );
};

export default ItemsColumn;
```
#### Define interfaces and initial data
```javascript
interface ITodoItem {
  id: number;
  title: string;
}

interface ColumnItem {
  id: number;
  title: string;
  items: {
    id: number;
    title: string;
  }[];
}

type ColumnType = { [key: string]: ColumnItem };

const initialTodoItems = [
  {
    id: 1,
    title: "Go for a walk",
  },
  {
    id: 2,
    title: "Take a nap",
  },
  {
    id: 3,
    title: "Read a book",
  },
  {
    id: 4,
    title: "Work out",
  },
  {
    id: 5,
    title: "Learn something new",
  },
];

const initialColumnData = {
  todoColumn: {
    id: 1,
    title: "To do",
    items: [...initialTodoItems],
  },
  doneColumn: {
    id: 2,
    title: "Done",
    items: [],
  },
};

```
#####Render the list component

```javascript
import { useState } from "react";
import { DragDropContext, DropResult } from "react-beautiful-dnd";
import ItemsColumn from "./itemsColumn";
import Droppable from "./droppable";

const TodoList = () => {
  const [columnData, setColumnData] = useState<ColumnType>(initialColumnData);

const onDragEnd = (result: DropResult) => {
}

  return (
    <div className="w-[800px] mx-auto">
      <p className="py-12 text-3xl text-center font-semibold text-blue-800">
        Todo List
      </p>
      <div className="grid grid-cols-2  gap-x-4 justify-between">
        <DragDropContext onDragEnd={onDragEnd}>
          {Object.entries(columnData).map(([id, column]) => (
            <Droppable droppableId={id} key={id}>
              {(provided) => (
                <div {...provided.droppableProps} ref={provided.innerRef}>
                  <ItemsColumn
                    columnTitle={column.title}
                    items={column.items}
                  />
                  {provided.placeholder}
                </div>
              )}
            </Droppable>
          ))}
        </DragDropContext>
      </div>
    </div>
  );
};

export default TodoList;
```
`DragDropContext` wraps a `Droppable` component and enables drag drop functionality. [Learn more about DragDropContext ](https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/drag-drop-context.md)

#####Functions for re ordering and dropping elements
```javascript
  const reorder = (
    list: Array<ITodoItem>,
    startIndex: number,
    endIndex: number
  ) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);

    return result;
  };

  const onDragEnd = (result: DropResult) => {
    const { source, destination } = result;

    // dropped outside the list
    if (!result.destination) {
      return;
    }

    const sInd = source.droppableId;
    const dInd = destination?.droppableId;

    // REORDER: if source and destination droppable ids are same
    if (dInd && sInd === dInd) {
      const column = columnData[sInd];
      const reorderedItems = reorder(
        column.items,
        source.index,
        destination.index
      );

      setColumnData({
        ...columnData,
        [dInd]: {
          ...column,
          items: reorderedItems,
        },
      });
    }

    // DROP: if source and destination droppable ids are different
    if (dInd && dInd !== sInd) {
      const sourceColumn = columnData[sInd];
      const desColumn = columnData[dInd];

      const itemToDrop = sourceColumn.items.find(
        (item) => item.id.toString() == result.draggableId
      );

      //INSERT: dragged item to another column
      if (itemToDrop) {
        const sourceColumnItems = Array.from(sourceColumn.items);
        const destColumnItems = Array.from(desColumn.items);

        sourceColumnItems.splice(result.source.index, 1);
        destColumnItems.splice(result.destination.index, 0, itemToDrop);

        setColumnData({
          ...columnData,
          [sInd]: {
            ...sourceColumn,
            items: sourceColumnItems,
          },
          [dInd]: {
            ...desColumn,
            items: destColumnItems,
          },
        });
      }
    }
  };
```
`onDragEnd` function checks if the item being dragged is dropped in a valid droppable and reorders or drops upon checking the source and destination `droppableId`.

#### What we have built :point_down:

![Todo List](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/53r5sn17l503g3qi926x.png)