Type to generate custom UI components with AI

Type to generate UI components from text

OR

Browse thousands of MUI, Tailwind, React components that are fully customizable and responsive.

Explore Components

How to Implement Drag and Drop in React with React DnD

Many web programs provide drag and drop capabilities, which allows users to effortlessly rearrange or move objects between lists. React drag and drop, popularly known as React DnD is a popular framework for implementing the drag and drop functionality in React, offering a collection of abstractions and components to manage complicated drag-and-drop behavior.

In this article, we’ll look at how to make use of React DnD to provide drag and drop capabilities in a React project. We’ll begin by creating a new project and install the required dependencies, then proceed to develop a basic drag-and-drop application. We’ll also go over some of the benefits and drawbacks of using React DnD, as well as some typical mistakes to avoid throughout implementation.


Understanding the Drag and Drop Concept

In the realm of user interfaces (UI), drag and drop transcends mere web aesthetics. It’s a powerful interaction paradigm that empowers a user to:

  • Effortlessly move and reorder items: Whether it’s rearranging tasks in a to-do list, reordering photos in a gallery, or organizing files in a folder, drag and drop provides an intuitive way to manipulate elements within an interface.

  • Simplify complex tasks: Uploading files becomes a breeze with drag and drop, eliminating the need for cumbersome navigation through menus and file selection dialogues.

  • Enhance user experience: The intuitive nature of drag and drop fosters a sense of control and ease, ultimately leading to a more enjoyable and efficient interaction with your application.

Imagine viewing an online photo album and easily rearranging photographs by dragging and dropping them into a new order. If you have used project management websites, design websites or website builders, you must have observed the drag and drop feature they incorporate. Example of websites that utilize drag and drop are Trello, Pinterest, Canva, and Wix.

For better understanding, let’s look at a breakdown of the drag and drop concept by looking at the core mechanisms of React Dnd using the online photo album analogy.

Photo Album Analogy

Drag and drop interactions are based on the browser’s built-in HTML5 Drag and Drop API. This API serves as the the platform, that gives events such as dragstart, dragover, and drop the signal to begin, progress, and complete a drag and drop action.

React DnD serves as an interface, connecting the browser API to your React component. It carefully watches for these drag and drop occurrences by placing event listeners on certain photo elements in your album. These events, like gentle nudges, execute callbacks set within your component using hooks like useDrag and useDrop.

When you drag a photo, information about it is bundled and attached to the drag event via the dataTransfer object. Picture this process as a baton pass; React DnD intercepts this data to ensure that everyone involved in the interaction (your components) understands which photo is being dragged.

To offer visual clues throughout the interaction, React DnD employs DOM manipulation techniques. Imagine dimming the dragged photo slightly as you move it or indicating the drop target region when hovering over it. These visual effects are coordinated by DOM manipulation, resulting in an intuitive user experience.

As you move the photo to a new location, React DnD components update the application’s state based on the events and your actions. This status update reflects changes in your online album, such as rearranging photographs or moving them to a certain album section. Basically, the state update saves the score, guaranteeing that your album matches the most recent arrangement.

Now that we have an understanding of these operations, we can proceed to explore the full potential of React DnD and develop intuitive and engaging interactions for our websites.

Introduction to React Drag and Drop

By now, we have sure gotten a grasp of what React Dnd is about. We’ve seen that it is an open-source library for adding drag-and-drop capabilities to React applications. It also provides abstractions and components for complicated drag-and-drop behavior, making it easier to create interactive and dynamic user interfaces.

Its major features are:

  • Its drag sources and drop targets features

    which will allow us to create custom drag sources and drop targets, tailoring them to our specific needs.

  • Its customization capabilities that allows us to design a preview that perfectly reflects the item, enhancing clarity for users of our application.

  • Its touch-friendly interactions since it is optimized for both desktop and touch-based devices, ensuring the drag-and-drop features work flawlessly on any platform.

  • Its many drag and drop effects allowing us to specify the desired behavior upon dropping an item and provide informative feedback throughout the drag operation.

These are the few features will talk about. As we progress, we’ll get to cover the others. It’s time to get our fingers dirty!

Setting Up a Project

1. Project Initialization

Create a new React project: Bootstrap a new react project with create-react-app or Vite.

Navigate to your project directory: Once the project is set up, navigate/cd to the project directory using your terminal.

For the examples in this article, we’ll be setting up our project with Vite and we’ll be working on an intuitive photo gallery website. We are going to turn the images in our gallery into draggable elements. We can drag and drop images on another section of the website. We’ll also be implementing drag and drop for mobile devices.

npm create vite@latest

You’ll be prompted to enter your project name.

Select the React framework and then select JavaScript. After this, a folder is created. Cd into the folder and run:

npm install

This adds the node_modules folder with its dependencies. Your folder structure should look like this:

Initial Folder Structure

2. Installing Necessary Packages

Next, we’ll install React dnd.

The core functionality of react dnd requires two packages which we’ve partially covered them till now.

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

react-dnd-html5-backend is the second package and it serves as the backend for the React dnd package, enabling browser-level support for drag and drop functionality through the HTML5 Drag and Drop API. React dnd provides the essential components and hooks for building drag and drop interactions within our React apps.

3. Clean up unwanted files and default code

If you’re familiar with setting up React projects then you agree with me that we need to do a little clean up. We’ll proceed to clean up our App.js and App.css files and import the libraries we installed. Our App.jsx file should look like this:

/*App.jsx*/

import React from 'react';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

const App = () => {
  return (
   <>
    <DndProvider backend={HTML5Backend}>
      {/*our components will be here*/} //to be replaced with the Gallery.jsx component
    </DndProvider>
   </>
  )
}

export default App

I know, I know, we’ve not started building our project and we’re already importing and writing a bunch of code! I’ll explain each line of code I added.

import { DndProvider, useDrag, useDrop } from 'react-dnd';

On this line, we’re importing DndProvider, useDrag and useDrop hooks from the react dnd library.

DndProvider is a component that comes with react-dnd. It wraps our application’s content and provides the necessary context for drag and drop functionality to work seamlessly.

useDrag and useDrop are hooks, also from the react-dnd library, used within react components to define draggable elements and drop targets, respectively.

import { HTML5Backend } from 'react-dnd-html5-backend';

On this line, we’re importing HTML5Backend from the react-dnd-html5-backend.

HTML5Backend: is a component that specifies the backend we’re using which is HTML5 Drag and Drop API in this case.

<DndProvider backend={HTML5Backend}>
      {/*our components will be here*/}
</DndProvider>

Importance of DnDProvider

On these lines of code, we’re wrapping our React components with the DndProvider component and passing HTML5Backend as a prop. Its like the central hub for managing drag and drop functionality. It plays the following very important roles:

  • It works as a context provider, providing all child components nested within it with necessary information for drag and drop operations.

  • It will act as a link between our application and the HTML5 drag and drop API backend we’ll be using. In order to manage browser-level events like dragstart, dragover, and drop, it communicates with the backend and initiates them when necessary while dragging and dropping objects.

  • It maintains the global state of the dragging and dropping interaction, including details about the currently dragged object and the presently dropped target, in order to carry out global state management. This shared state makes it possible for any child component of the DndProvider to obtain and make use of a specific data on the current interaction.

  • It manages the application’s drag and drop lifecycle as a whole. It performs callbacks that are defined using DnD hooks  at different points throughout the interaction (e.g., when an item is dropped, begins to be dragged, or hovers over a drop target).

With these explained, let’s go over to the fun part where we start building our image gallery project.

Building a Project

We’ll start by creating an array of objects representing our image gallery. We’ll be using random images.

const images = [
  { id: 1, src: 'flower.jpg', title: 'Image 1' },
  { id: 2, src: 'cat.jpg', title: 'Image 2' },
  { id: 3, src: 'dog.jpg', title: 'Image 2' },
  // ... add as many as you want
];

Creating our components

We’ll be creating two new components:
Gallery: Container component for the entire gallery with logic for managing image data and updating state.

DraggableImage: Component representing each individual image, responsible for handling drag and drop events, and rendering the image.

Implementing our DnD application

We’re going to start by mapping through our images in the Gallery.jsx component, and then pass the props(src, title, and id) to the DraggableImage component:

/*Gallery.jsx*/ 
  
   {Images.map((image) => (
          <div className="images">
            <DraggableImage
              key={image.id}
              src={image.src}
              id={image.id}
              title={image.title}
            />
          </div>
        ))}

Next, in our DraggableImage.jsx component, we’ll destructure the props and use them in an img tag.

/*DraggableImage.jsx*/

const DraggableImage = ({ src, title, id }) => {
   return(
     <img
        src={src}
        alt={title}
     />
   )
} 

We’ll be using the id prop soon.

useDrag hook

Next, we’ll use the useDrag hook to make each image a draggable item:

import { useDrag } from 'react-dnd';

...
const [{ isDragging }, drag] = useDrag(() => ({
    type: 'image',
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging,
    }),
  }));

type and collect are properties that come from the useDrag hook.

type: ‘image’: Specifies the type of data being dragged, which is “image.” Note that “image” can be any other string like “photo” or whatever meaningful name you think of. Its only used to specify the type property.

collect: (monitor) => ({ isDragging: !!monitor.isDragging }): is a function that gathers information about the dragging state. It inverts the monitor.isDragging value, setting isDragging to true only when the image is not already being dragged.

To make our image draggable, we’ll have to add the drag prop from the hook as a ref on the img element.

<img
	ref={drag}
        src={src}
        alt={title}
      />

Furthermore, we’ll use the isDragging prop from the drag hook to style the image when it is dragged:

<img
... 
	style={{
          border: isDragging ? '3px solid brown' : '0px',
	}}
/>

I want to share a useful resource with you before we move forward. Have you heard of PureCode.ai? It’s a developer-built platform driven by AI that could streamline your application development process. Make sure to visit PureCode.ai right now!

Droppable Interface

In our Gallery.jsx file, we’ll add a new div for the droppable interface (our album). Our goal is to be able to drag our image and drop it on another div or section of our screen which serves as our album.

/*Gallery.jsx*/
 <div className="dropContainer">
        <h2>Album</h2>
     </div>

Next, we’ll add a state for the album using react useState hook. It holds an array to store the currently dragged item.

import React, { useState } from 'react';
const Gallery = () => {
const [album, setAlbum] = useState([]);
...
}

Just as we mapped through the images array, we’ll map through the album array so that the currently dropped image is shown here. Similarly, we’ll pass the props from the album array to the DraggableImage component.

<div className="dropContainer">
        <h2>Album</h2>
        {album.map((image) => (
          <DraggableImage key={image.id} src={image.src} title={image.title} />
        ))}
 </div>

Making Images Droppable

Now, we’ll work on making our images droppable using the useDrop hook. It will define a drop target within the album area.

import { useDrop } from 'react-dnd';

The useDrop hook also provides properties that we can use to make our images draggable items.

  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'image',
    drop: (item) => addImageToAlbum(item.id),
    collect: (monitor) => ({
      isOver: !!monitor.isOver,
    }),
  }));
  • accept: ‘image’: Specifies that this drop target only accepts items of type “image”, because we specified type as image previously in the DraggableImage component.

  • drop: (item) => addImageToAlbum(item.id): this function is triggered when an image is dropped on the target. It has access to the id of every image therefore, it calls the addImageToAlbum function (which we’ll see soon), passing the image id.

  • collect: (monitor) => ({ isOver: !!monitor.isOver }): this function gathers information about the drop target state. It checks if the drop target is currently being hovered over by an image and sets the isOver state accordingly.

Now, we need to add the drop function as a ref on the album’s div.

   <div className="dropContainer" ref={drop}>
	...
   </div

The addImageToAlbum function filters the Images array to find the image with the provided id. It then updates the album state with the filtered image array, essentially adding the image to the Album.

  const addImageToAlbum = (id) => {
    const images = Images.filter((image) => id === image.id);
    setAlbum([images[0]]);
  };

In the DraggableImage.jsx file, we’ll pass the id prop to the drag hooks’ item property to specify the data being dragged. Now, both the drag and drop hooks will have access to the id of the image being dragged or dropped. Our updated code should look like this:

const [{ isDragging }, drag] = useDrag(() => ({
    type: 'image',
    item: { id: id }, //item property
    collect: (monitor) => ({
      isDragging: !monitor.isDragging,
    }),
  }));

Dnd Project Logic

With this, we’ve completed our online image gallery project! Let’s recall the logic of our DnD project.

  • The App.jsx component wraps the DndProvider around the Gallery.jsx component to ensure that all components within the App component have access to the functionalities provided by React DnD, including drag and drop context and hooks like useDrag and useDrop

  • The Gallery component renders the available images as DraggableImage components within the “Images” section.

  • When a user drags an image and drops it onto the “Album” section, the useDrop hook triggers the addImageToAlbum function, adding the selected image to the album state. This state update then re-renders the “Album” section, displaying the newly added image.

  • The DraggableImage component uses the isDragging state to visually indicate when an image is being dragged by applying a border style.

Below is the complete code for the Gallery and DraggableImage components:

/*Gallery.jsx*/
import React, { useState } from 'react';
import DraggableImage from './DraggableImage';
import { useDrop } from 'react-dnd';
const Gallery = () => {
  const [album, setAlbum] = useState([]);
  const [{ isOver }, drop] = useDrop(() => ({
    accept: 'image',
    drop: (item) => addImageToAlbum(item.id),
    collect: (monitor) => ({
      isOver: !!monitor.isOver,
    }),
  }));
  const addImageToAlbum = (id) => {
    const images = Images.filter((image) => id === image.id);
    setAlbum([images[0]]);
  };
  return (
    <div className="gallery-container">
      <h2>Images</h2>
      <div className="images-container">
        {Images.map((image) => (
          <div className="images">
            <DraggableImage
              key={image.id}
              src={image.src}
              id={image.id}
              title={image.title}
            />
          </div>
        ))}
      </div>
      <div className="dropContainer" ref={drop}>
        <h2>Album</h2>
        {album.map((image) => (
          <DraggableImage key={image.id} src={image.src} title={image.title} />
        ))}
      </div>
    </div>
  );
};
export default Gallery;
/*DraggableImage.jsx*/
import React from 'react';
import { useDrag } from 'react-dnd';
const DraggableImage = ({ src, title, id }) => {
  const [{ isDragging }, drag] = useDrag(() => ({
    type: 'image',
    item: { id: id },
    collect: (monitor) => ({
      isDragging: !monitor.isDragging,
    }),
  }));
  return (
    <>
      <img
        ref={drag}
        src={src}
        alt={title}
        key={id}
        style={{
          border: isDragging ? '3px solid brown' : '0px',
          width: '100%',
          height: '100%',
          borderRadius: '10px',
          cursor: 'grab'
        }}
      />
    </>
  );
};
export default DraggableImage;

Our application should look like this:

Touch Devices

While there is a mobile version for mobile devices, dragging won’t feel very natural; it only updates instead of providing a satisfying draggable experience. However, using the react-dnd-touch-backend library, we can make it work on our mobile devices.

npm i react-dnd-touch-backend

Next, we’ll check if the device is a mobile device or if it has touch support.

In App.jsx;

const isDeviceTouch = () => {
  if ("ontouchstart" in window) {
    return true;
  }
  return false;
};

Next, we’ll import the TouchBackend function and use the isDeviceTouch method to return the TouchBackend function if the device is a touch device or return the HTML5Backend function if its not.

import TouchBackend from "react-dnd-touch-backend";

const DNDBackend = isDeviceTouch() ? TouchBackend : HTML5Backend;

Now, we’ll replace the backend prop with DNDBackend.

    <div className="App">
      <DndProvider backend={DNDBackend}>
        <Gallery />
      </DndProvider>
    </div>

This solves the mobile device drag and drop limitation.

Pros and Cons of React DnD Library

Like any tool, React DnD comes with its own set of advantages and disadvantages. Let’s look at them in the table below.

FeatureProsCons
Development EaseSimplifies drag and drop development compared to raw API usageLearning curve is steep and involves understanding concepts like drag sources, drop targets, hooks, and state management
FlexibilityOffers customizable drag previews, different drag effects (“copy,” “move,” “link”), and touch device supportMight introduce some complexity to your project
ComposabilityEnables creation of custom drag sources and drop targetsAdds dependency and potentially increases bundle size
AccessibilityAdheres to WCAG guidelinesMay require additional effort to ensure overall application accessibility

Alternative React drag and drop Libraries

Apart from react-dnd there are other libraries available.

1. react-beautiful-dnd:

  • Strengths:

    • Highly-rated for its flexibility, accessibility, and performance optimization.

    • Offers features like horizontal/vertical dragging, grid layouts, and advanced customization options.

    • It integrates well with virtual scrolling libraries for performance in large datasets.

  • Weaknesses:

    • It has slightly steeper learning curve compared to some alternatives.

    • Its API is more complex compared to simpler libraries.

2. react-sortable-hoc:

  • Strengths:

    • It is a lightweight library focused on basic reordering functionality.

    • Simple to learn and use for basic sorting and reordering needs.

    • Supports touch devices and various list item types.

  • Weaknesses:

    • Lacks advanced features like custom drag previews and drag effects.

    • Limited customization options compared to other libraries.

3. SortableJS (with React integration):

  • Strengths:

    • Versatile library that works with vanilla JavaScript and can be integrated with React.

    • Supports a wide range of features like sorting, dragging between lists, and touch devices.

    • Large community and extensive documentation available.

  • Weaknesses:

    • Requires additional configuration and integration effort for React usage compared to native React libraries.

    • Might introduce additional bundle size due to being a vanilla JavaScript library.

4. react-dnd-touch-backend:

  • Strengths:

    • Designed specifically for touch-based devices, offering optimized drag and drop interactions.

    • Can be used in conjunction with other libraries like react-dnd for combined functionality.

  • Weaknesses:

    • Limited functionality on its own, primarily focused on touch handling.

    • Requires additional setup and potential conflicts with other libraries.

Common Errors Encountered in DnD Implementation

There are some common errors you might encounter while working on a Dnd react element. Let’s look at some of them below:

1. Missing or Incorrect Dependencies:

  • Error: errors relating to libraries like react-dnd or its backend (react-dnd-html5-backend) not properly installed or imported in your project.

  • Solution: Ensure you have installed the required libraries using npm or yarn and imported them correctly in your components.

2. Incorrect Use of Hooks:

  • Error: Errors from using hooks like useDrag and useDrop outside of functional components, providing invalid data to the hook, or not handling state updates appropriately.

  • Solution: Double-check the documentation for the specific library you’re using to ensure you’re utilizing the hooks correctly. Pay attention to required arguments, returned data, and state management practices.

3. Missing or Incorrect DndProvider:

  • Error: Drag and drop interaction not working.

  • Solution: Ensure you wrap your application or the components that require drag and drop functionality with the DndProvider component, setting the appropriate backend.

4. State Management Issues:

  • Error: Unexpected behavior or visual glitches. Errors like dragged item disappearing, not updating in the drop target, or incorrect re-rendering.

  • Solution: Carefully manage the state of your components, updating it based on events from the drag and drop hooks and ensuring consistent data flow throughout the application.

Best Tips and Practices

  • Utilize hooks like useDrag and useDrop effectively to define draggable elements and drop targets.

  • Maintain a clear understanding of how your component’s state interacts with the drag and drop events.

  • Ensure your drag and drop implementation adheres to WCAG guidelines to provide an inclusive experience for users with disabilities.

  • Be mindful of potential performance implications, especially when dealing with large datasets.

  • Implement a comprehensive testing strategy that covers various drag and drop scenarios, including different browsers, devices, and user interactions.

Final Thoughts on React DnD

We’ve seen that React DnD provides a valuable tool for building interactive and engaging drag and drop experiences in React applications. Carefully consider your project’s specific needs and weigh the strengths and weaknesses of React DnD against alternative libraries to make an informed decision. Remember to follow best practices for accessibility, performance optimization, and testing to ensure a positive user experience.

PureCode.ai exists for you, to make your coding journey seamless and exciting. Check out PureCode.ai‘s components today!

Favourite Jome

Favourite Jome