Using Dnd-Kit Basics
Tushar Tushar

Tushar Tushar @tushar_sandhu

About: Comp sci graduate who is still learning

Location:
Kamloops, British Columbia, Canada
Joined:
Dec 17, 2024

Using Dnd-Kit Basics

Publish Date: May 31
0 0

if you want to create draggable/droppable elements on your website one of the options available is Dnd-kit which is a modern drag-and-drop toolkit for React. Using it is fairly very easy, unless you end up on youtube where for some reason every one wants to make kanban boards with it and suddenly instead of learning how to use Dnd-kit now you are also dealing with the added complexity of a kanban board. So, I am just going to do a very simple and straightforward setup for Dnd-kit just going over how to use it and what we are doing.

Don't want to read just want the code? Here is the 🔗 git link

Let's Breakdown our steps

  1. 🛠️ Create a React Project with Vite.
  2. 🪄 Adding Dnd-context
  3. 👀 Do some very Basic page setup like giving the page full height/width etc.
  4. 🖱️ Making a Draggable Item.
  5. 📦 Making a Droppable Item.
  6. 🔨 Import both the elements.
  7. 🧠 Telling Dnd what to do on starting and ending the drag.
  8. 🔜 Playing with DragOverlay to have smooth transitions (coming soon, stay tuned).

1. 🛠️ Creating project with vite

Use npm create vite@latest . -- --template react
if you are already in the folder where you wish to create or try or
npm create vite@latest my-proj -- --template react which will create a directory with name my-proj.
In the last command the . denoted that we want to create this react project in the directory we are in.

run npm install to install the required node packages.

Remove everything from the App.jsx it should look something like this.

import "./App.css";
function App() {
  return <>Hello</>;
}

export default App;

Enter fullscreen mode Exit fullscreen mode

This will give us a clean slate to work with.
I have kept the default styles from the template because I am lazy.

2. 🪄 Installing Dnd-kit and wrapping in DndContext

We can use npm install @dnd-kit/core to install Dnd-kit to our project.

once installed we can wrap our webapp in DndContext which is like the central controller for Dnd-kit, and without this drag and drop will not work Also add some containers to keep our items in the future. Our App.jsx will look something like this.

import { DndContext } from "@dnd-kit/core";
import "./App.css";

function App() {
  return (
    <>
      <DndContext>
        <div className="webapp">
          <div className="droppable-container"></div>

          <div className="draggable-container"></div>
        </div>
      </DndContext>
    </>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

3. 👀 Basic Page Setup 👀

I just created 2 containers one will hold all the draggable items initially and all elements will be dropped in to the droppable container which we will change soon. This is how the App.css looks like after the changes.

body {
  height: 100vh;
  width: 100vw;
  margin: 0;
  padding: 0;
}
#root {
  height: 100%;
  width: 100%;
}

.webapp {
  height: 100%;
  width: 100%;
  display: flex;
  gap: 5%;
  justify-content: space-evenly;
  align-items: center;
}
.droppable-container {
  outline: green solid;
  width: 40%;
  height: 80%;
  display: grid;
  grid-template-columns: auto auto auto;
  align-items: center;
  justify-content: space-evenly;
}

.draggable-container {
  outline: solid red;
  width: 40%;
  height: 80%;
  display: grid;
  grid-template-columns: auto auto auto;
  align-items: center;
  justify-content: space-evenly;

}

.drag-item {
  background-color: antiquewhite;
  height: 100px;
  width: 100px;
  cursor: pointer;
  color: black;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

Enter fullscreen mode Exit fullscreen mode

4. 🖱️ Let's Create a simple Draggable component

Create a components directory in src and a 'draggableItem' directory within this directory.
Then create a DraggableItem.jsx file.
As mentioned most of the heavy lifting gets done by Dnd-kit so following the documentation the Draggable Item will look something like this.

import { useDraggable } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
const Draggable = (props) => {
  const { attributes, listeners, setNodeRef, transform } = useDraggable({
    id: props.id,
  });
  const style = {
    transform: CSS.Translate.toString(transform),
  };

  return (
    <div
      className="drag-item"
      style={style}
      ref={setNodeRef}
      {...listeners}
      {...attributes}
    >
      {props.children}
    </div>
  );
};

export default Draggable;

Enter fullscreen mode Exit fullscreen mode

The props.id is the most important as it defines what element becomes draggable if you plan on having just one then you can hard assign the id in the component but if you are creating multiple then it is better to receive it from the parent.
and using const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: props.id, }); spreads all the needed stuff from Dnd-kit.

and this line const style = { transform: CSS.Translate.toString(transform), }; makes the element visually move on the screen.

and here we basically assign everything to our draggable container

return (
    <div
      className="drag-item"
      style={style}
      ref={setNodeRef}
      {...listeners}
      {...attributes}
    >
      {props.children}
    </div>
  );

Enter fullscreen mode Exit fullscreen mode

props.children is basically everything that we will wrap under the Draggable component in the main file App.jsx

5. 📦 Let's create a Droppable component

Once done the Droppable.jsx should look something like this.

import { useDroppable } from "@dnd-kit/core";
const Droppable = (props) => {
  const { isOver, setNodeRef } = useDroppable({
    id: "drop-container",
  });
  return (
    <div className="droppable-container" ref={setNodeRef}>     
      {props.children}    
    </div>
  );
};

export default Droppable;

Enter fullscreen mode Exit fullscreen mode

Here we only want a single droppable container so we can assign the id directly here. and the isOver tells DndContext if an element is over this droppable container, and once we drop the item on this container we use props.children to render all the dropped items in the droppable container.

6. 🔨 Let's import both the elements to App.jsx

I have also created an array of some items so we can have multiple draggable items and then we can map over them to create the draggable items we need unique id's for all so we will use the index to create these Draggable items.

import { DndContext } from "@dnd-kit/core";
import "./App.css";
import Droppable from "./components/droppableItem/Droppable";
import Draggable from "./components/draggableItem/Draggable";
import { useState } from "react";
function App() {
  const [items, setItems] = useState(["Cat", "Dog", "Mansion", "Car", "Lake"]);

  return (
  <>
      <DndContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
        <div className="webapp">
            <Droppable>
              {droppedItems.map((id) => (
                <Draggable key={id} id={id}>
                  {`${id}`}
                </Draggable>
              ))}
            </Droppable>
          <div className="draggable-container">
            {items.map((id) => (
              <Draggable key={id} id={id}>
                {`${id}`}
              </Draggable>
            ))}
          </div>
        </div>
      </DndContext>
    </>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

We can also make some styling changes to the Draggable container so that all elements are spread out properly

.draggable-container {
  outline: solid red;
  width: 40%;
  height: 80%;
  display: grid;
  grid-template-columns: auto auto auto;
  align-items: center;
  justify-content: space-evenly;
}

Enter fullscreen mode Exit fullscreen mode

and also style the drag Items.

.drag-item {
  background-color: antiquewhite;
  height: 100px;
  width: 100px;
  cursor: pointer;
  color: black;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

Enter fullscreen mode Exit fullscreen mode

At this point you should be able to at least drag around your items.
But you won't be able to drop them anymore, that is because we still have not configured the Drop logic. let's get to it.

7. 🧠 Telling DND what to do on Drag start and End.

To do so we will create 2 functions onDragStart and onDragEnd and create some state variables const [droppedItems, setDroppedItems] = useState([]) This will hold the list of all the elements dropped in the container. const [activeId, setActiveId] = useState(null) and this will keep track of what element we are dragging. and set them in the dndContext.

  const [droppedItems, setDroppedItems] = useState([])
  const [activeId, setActiveId] = useState(null)

Enter fullscreen mode Exit fullscreen mode

<DndContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
first on dragStart we need to set the activeId to the element being dragged.

  const onDragStart = (event) =>{
    setActiveId(event.active.id)
  }

Enter fullscreen mode Exit fullscreen mode

And when the drag ends
We need to check if it was dropped over our droppable container or not and if it was then check if that item already existed in there or not we do not want to duplicate the same element, then we need to remove the dragged element from the items list to the droppedItems list and remember to remove the activeId.
Something like this,

  const onDragEnd = (event) => {
    const {active, over} = event
    if (!over) return
    if(over.id ==='drop-container'){
        setItems(items => items.filter(item => item!= activeId))
        setDroppedItems([activeId, ...droppedItems])
    }
    setActiveId(null)
  }

Enter fullscreen mode Exit fullscreen mode

After this you should be able to drop your elements to the droppable container, but you cannot remove them from it, but it is really easy to do so as well.

This time:

  1. We will check if the item was dropped over nothing (outside the container).
  2. Check if that element was inside the droppable container before the drag or not.
  3. if not, do nothing element was out stays out
  4. if yes, remove from the droppedItems list and add back to the items list.

And now your dragEnd function should look something like this.

  const onDragEnd = (event) => {
    const {active, over} = event
    if (!over){
      if (droppedItems.includes(activeId)){
        setDroppedItems((items) => items.filter((item) => item != activeId));
        setItems([...items, activeId])
      }
      setActiveId(null)
      return
    }
    if(over.id ==='drop-container' && !droppedItems.includes(activeId)){
        setItems(items => items.filter(item => item!= activeId))
        setDroppedItems([activeId, ...droppedItems])
    }
    setActiveId(null)
  }

Enter fullscreen mode Exit fullscreen mode

and you should be able to drag and drop stuff in the container and outside the container to remove it.

8.🧠 Playing with DragOverlay to have smooth transitions

We can also animate the drag and drop of the elements with the help of dragOverlay which is also very easy. I will be creating a new article for the same soon, so stay tuned for that :)

Feel free to drop a comment in case something seems wrong or you are unable to replicate the same functionality, would love to help 😄.
In the end here is all the code and the 🔗 git link

App.jsx

import { DndContext } from "@dnd-kit/core";
import "./App.css";
import Droppable from "./components/droppableItem/Droppable";
import Draggable from "./components/draggableItem/Draggable";
import { useState } from "react";
function App() {
  const [items, setItems] = useState(["Cat", "Dog", "Mansion", "Car", "Lake"]);
  const [droppedItems, setDroppedItems] = useState([])
  const [activeId, setActiveId] = useState(null)

  const onDragStart = (event) =>{
    setActiveId(event.active.id)
  }

  const onDragEnd = (event) => {
    const {active, over} = event
    if (!over){
      if (droppedItems.includes(activeId)){
        setDroppedItems((items) => items.filter((item) => item != activeId));
        setItems([...items, activeId])
      }
      setActiveId(null)
      return
    }
    if(over.id ==='drop-container' && !droppedItems.includes(activeId)){
        setItems(items => items.filter(item => item!= activeId))
        setDroppedItems([activeId, ...droppedItems])
    }
    setActiveId(null)
  }

  return (
    <>
      <DndContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
        <div className="webapp">
            <Droppable>
              {droppedItems.map((id) => (
                <Draggable key={id} id={id}>
                  {`${id}`}
                </Draggable>
              ))}
            </Droppable>
          <div className="draggable-container">
            {items.map((id) => (
              <Draggable key={id} id={id}>
                {`${id}`}
              </Draggable>
            ))}
          </div>
        </div>
      </DndContext>
    </>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Droppable.jsx

import { useDroppable } from "@dnd-kit/core";
const Droppable = (props) => {
  const { isOver, setNodeRef } = useDroppable({
    id: "drop-container",
  });
  return (
    <div className="droppable-container" ref={setNodeRef}>

      {props.children}

    </div>
  );
};

export default Droppable;

Enter fullscreen mode Exit fullscreen mode

Draggable.jsx

import { useDraggable } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
const Draggable = (props) => {
  const { attributes, listeners, setNodeRef, transform } = useDraggable({
    id: props.id,
  });
  const style = {
    transform: CSS.Translate.toString(transform),
  };

  return (
    <div
      className="drag-item"
      style={style}
      ref={setNodeRef}
      {...listeners}
      {...attributes}
    >
      {props.children}
    </div>
  );
};

export default Draggable;

Enter fullscreen mode Exit fullscreen mode

App.css

body {
  height: 100vh;
  width: 100vw;
  margin: 0;
  padding: 0;
}
#root {
  height: 100%;
  width: 100%;
}

.webapp {
  height: 100%;
  width: 100%;
  display: flex;
  gap: 5%;
  justify-content: space-evenly;
  align-items: center;
}
.droppable-container {
  outline: green solid;
  width: 40%;
  height: 80%;
  display: grid;
  grid-template-columns: auto auto auto;
  align-items: center;
  justify-content: space-evenly;
}

.draggable-container {
  outline: solid red;
  width: 40%;
  height: 80%;
  display: grid;
  grid-template-columns: auto auto auto;
  align-items: center;
  justify-content: space-evenly;

}

.drag-item {
  background-color: antiquewhite;
  height: 100px;
  width: 100px;
  cursor: pointer;
  color: black;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

Enter fullscreen mode Exit fullscreen mode

Cover image taken from craiyon AI

Comments 0 total

    Add comment