DEV Community

Tasfia Islam
Tasfia Islam

Posted on

React js drag and drop todo list

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, then install tailwind css framework.
Next install 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

Droppable Component
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;
Enter fullscreen mode Exit fullscreen mode

The Droppable component defines a drop zone for draggable items.
Learn more about Droppable

Draggable Card
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;

Enter fullscreen mode Exit fullscreen mode

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

Column Component
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;
Enter fullscreen mode Exit fullscreen mode

Define interfaces and initial data

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: [],
  },
};

Enter fullscreen mode Exit fullscreen mode
Render the list component
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;
Enter fullscreen mode Exit fullscreen mode

DragDropContext wraps a Droppable component and enables drag drop functionality. Learn more about DragDropContext

Functions for re ordering and dropping elements
  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,
          },
        });
      }
    }
  };
Enter fullscreen mode Exit fullscreen mode

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 👇

Todo List

Top comments (4)

Collapse
 
mezgoodle profile image
Maksym Zavalniuk

Very nice post! Keep going!

Collapse
 
tasfiaislam profile image
Tasfia Islam

Thank you! Sure will come up with more posts soon.

Collapse
 
hafizulhaque profile image
Hafizul Haque

Nice Work!
Could you share the full codebase in github or live preview if there's any?

Collapse
 
tasfiaislam profile image
Tasfia Islam

You can check out this repository
github.com/TasfiaIslam/Draggable-todo