Editor’s note: This tutorial was last updated on 3 November 2023 to include advanced drag-and-drop features, as well as alternative tools for implementing drag-and-drop in React, such as React Smooth DnD.
React DnD brings draggable elements to HTML, enabling developers to build applications that contain rich UI elements that can be dragged from one place to another. React DnD is an integral part of most modern applications, providing richness in UI without comprising the UX.
The most common use cases for drag and drop in React include uploading files, moving items between multiple lists, and rearranging images and assets. In this tutorial, we’ll focus on several different tools and use cases for implementing drag and drop in React, with a focus on the React DnD library.
React DnD simplifies the implementation of drag-and-drop interactions by providing a set of abstractions and components to handle complex drag-and-drop behavior, including the following features:
React DnD also meets WCAG (Web Content Accessibility Guidelines), so it can be made accessible to users with disabilities, ensuring an inclusive user experience. With React DnD, we can create beautiful apps like the chess app below:
React DnD works for almost all use cases, like grids, one-dimensional lists, and more. Additionally, it has a very powerful API to add any customization to drag-and-drop in React.
For small examples, the React DnD API is very easy to get started with. However, it can be tricky to add complex customizations. The learning curve is also higher and more complex than other libraries, such as react-beautiful-dnd. Additionally, some hacks are required to support both web and touch devices.
Now let’s build a simple drag-and-drop application with React DnD.
Before we dive into the drag-and-drop code, we need to first understand how React DnD works.
React DnD can make any element draggable and droppable. To achieve this, React DnD needs to have the references of all droppable items. All elements that are draggable and droppable need to be enclosed inside React DnD’s context provider, which is used for initializing and managing the internal state.
We don’t need to worry too much about how it manages state because React DnD includes easy APIs to expose these states, enabling us to compute and update our local states.
To install React DnD into your React app, run either of the commands below:
yarn add react-dnd react-dnd-html5-backend immutability-helper //or npm install react-dnd react-dnd-html5-backend immutability-helper
Keep in mind that this example won’t work until you complete all the steps. Check out the repo for React DnD v16 here.
First, we’ll enclose our ImageList
component inside a DnD context provider:
/* filename: App.js */ import { DndProvider } from "react-dnd"; import {HTML5Backend} from "react-dnd-html5-backend"; function App() { ... return ( <main className="App"> ... <DndProvider backend={HTML5Backend}> <ImageList images={images} moveImage={moveImage}/> <DndProvider> </main> ); }
Next, import the DNDProvider
and initialize it with the backend
props. This is the variable that helps us choose which API to use for drag and drop.
Now, we need to add the items as draggable and droppable. In our application, both draggable and droppable items are the same. We’ll drag the Image
component and drop it onto another Image
component, making our job a little easier. To implement this, use the code below:
import React, { useRef } from "react"; // import useDrag and useDrop hooks from react-dnd import { useDrag, useDrop } from "react-dnd"; const type = "Image"; // Need to pass which type element can be draggable, its a simple string or Symbol. This is like an Unique ID so that the library know what type of element is dragged or dropped on. const Image = ({ image, index }) => { const ref = useRef(null); // Initialize the reference // useDrop hook is responsible for handling whether any item gets hovered or dropped on the element const [, drop] = useDrop({ // Accept will make sure only these element type can be droppable on this element accept: type, hover(item) { ... } }); // useDrag will be responsible for making an element draggable. It also expose, isDragging method to add any styles while dragging const [{ isDragging }, drag] = useDrag(() => ({ // what type of item this to determine if a drop target accepts it type: type, // data of the item to be available to the drop methods item: { id: image.id, index }, // method to collect additional data for drop handling like whether is currently being dragged collect: (monitor) => { return { isDragging: monitor.isDragging(), }; }, })); /* Initialize drag and drop into the element using its reference. Here we initialize both drag and drop on the same element (i.e., Image component) */ drag(drop(ref)); // Add the reference to the element return ( <div ref={ref} style={{ opacity: isDragging ? 0 : 1 }} className="file-item" > <img alt={`img - ${image.id}`} src={image.src} className="file-img" /> </div> ); }; const ImageList = ({ images, moveImage }) => { const renderImage = (image, index) => { return image ? ( <Image image={image} index={index} key={`${image.id}-image`} moveImage={moveImage} /> ): null; }; return <section className="file-list">{images.map(renderImage)}</section>; export default ImageList;
Now, our images are draggable. But, if we drop an image, it will return to its original position because useDrag
and useDrop
will handle it until it is dropped. Unless we change our local state, the image will return to its original position.
To update the local state, we need to know the dragged element and the hovered element, which is the element on which the dragged element is hovered. useDrag
exposes this information through the hover
method:
const [, drop] = useDrop({ // accept receives a definition of what must be the type of the dragged item to be droppable accept: type, // This method is called when we hover over an element while dragging hover(item) { // item is the dragged element if (!ref.current) { return; } const dragIndex = item.index; // current element where the dragged element is hovered on const hoverIndex = index; // If the dragged element is hovered in the same place, then do nothing if (dragIndex === hoverIndex) { return; } // If it is dragged around other elements, then move the image and set the state with position changes moveImage(dragIndex, hoverIndex); /* Update the index for dragged item directly to avoid flickering when the image was half dragged into the next */ item.index = hoverIndex; } });
The hover
method is triggered whenever an element is dragged and hovers over this element. In this way, when we start dragging an element, we get the index of that element and the element we are hovering on. We’ll pass this dragIndex
and hoverIndex
to update our image’s state.
At this point, you might be wondering why we need to update the state while hovering. Why not update it while dropping? It’s possible to just update the state while dropping. drag-and-drop
will work and rearrange the positions, but the UX won’t be good.
For example, if you drag one image over another image and we immediately change the position, then that will give good feedback to the users who are dragging it. Otherwise, they might not know whether the drag functionality is working until they drop the image in some position. Here’s an example:
Therefore, we should update the state on every hover. While hovering over another image, we set the state and change the positions, and the user will see a nice animation.
So far, we’ve just shown the code for updating the state as moveImage
. Now, let’s see the implementation:
/* filename: App.js */ import update from "immutability-helper"; ... const moveImage = (dragIndex, hoverIndex) => { // Get the dragged element const draggedImage = images[dragIndex]; /* - copy the dragged image before hovered element (i.e., [hoverIndex, 0, draggedImage]) - remove the previous reference of dragged element (i.e., [dragIndex, 1]) - here we are using this update helper method from immutability-helper package */ setImages( update(images, { $splice: [[dragIndex, 1], [hoverIndex, 0, draggedImage]] }) ); }; // We will pass this function to ImageList and then to Image -> Quite a bit of props drilling, the code can be refactored and place all the state management in ImageList itself to avoid props drilling. It's an exercise for you :)
Now, our app is fully functional on HTML5 onDrag
event-supported devices. Unfortunately, it won’t work on touch devices.
As I said before, we can support touch devices using a utility function. It’s not the best solution, but it works. The experience of dragging won’t be great on touch devices — it simply updates, but you won’t feel like you’re dragging. You can also make it clean:
import HTML5Backend from "react-dnd-html5-backend"; import TouchBackend from "react-dnd-touch-backend"; // simple way to check whether the device support touch (it doesn't check all fallback, it supports only modern browsers) const isTouchDevice = () => { if ("ontouchstart" in window) { return true; } return false; }; // Assigning backend based on touch support on the device const backendForDND = isTouchDevice() ? TouchBackend : HTML5Backend; ... return ( ... <DndProvider backend={backendForDND}> <ImageList images={images} moveImage={moveImage} /> </DndProvider> ) ...
react-beautiful-dnd is another popular third-party library for implementing drag-and-drop interactions in React applications, especially when dealing with ordered lists, grids, and other collections of items.
react-beautiful-dnd offers a high-level, declarative API and contains functionalities such as:
If you’d like to read about how to create a drag-and-drop example using react-beautiful-dnd, check out this video tutorial.
React-Grid-Layout is a grid layout system built exclusively for React. Unlike similar systems like Packery and Gridster, React-Grid-Layout is responsive and supports breakpoint layouts, which can be provided by the user or autogenerated.
React-Grid-Layout does not require jQuery. It allows you to create draggable grid items that can be moved within the grid by dragging and dropping into different positions.
React-Draggable is a popular and lightweight React library that allows you to make DOM elements draggable within a specified container.
With this library, you can customize the appearance and behavior of the draggable element, such as setting initial position, containment, and enabling or disabling dragging as needed. It also supports grid-based snapping, allowing you to align the draggable element to a grid or predefined snap points, while also allowing you to specify the boundaries within which the element can be dragged, ensuring it stays within a defined area.
react-smooth-dnd is a popular third-party library for adding smooth and visually pleasing drag-and-drop interactions to your React applications. It builds upon the core features of React DnD and provides a higher-level API that simplifies the implementation of drag-and-drop behavior with smooth animations and transitions.
react-smooth-dnd is particularly useful when you want to create lists with smooth reordering animations or when you need to implement drag-and-drop interactions in a visually appealing manner.
The examples above cover the basic implementation of drag-and-drop functionalities, but there are more advanced drag-and-drop features in React. Here are a few:
Drag and clone: This feature creates a duplicate of an item in place after it is dragged. This leaves the original item in place, enabling actions like creating duplicates or maintaining a reference to the original.
There’s no built-in support for this feature out of the box with React DnD, so if you want to implement it, it’s best you use react-beautiful-dnd, which has a built-in drag and clone feature that can be easily implemented with the code below:
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; // ... <DragDropContext onDragEnd={handleDragEnd}> <Droppable droppableId="list"> {(provided) => ( <ul {...provided.droppableProps} ref={provided.innerRef}> {items.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ background: snapshot.isDragging ? 'lightblue' : 'white', ...provided.draggableProps.style, }} > {item.content} </div> )} </Draggable> ))} {provided.placeholder} </ul> )} </Droppable> </DragDropContext>
Custom animations: You can apply custom animations during drag-and-drop actions, including smooth transitions, visual effects, or transformations to enhance the user experience.
react-beautiful-dnd, in particular, provides flexibility to incorporate animations and transitions directly into your drag-and-drop components, making it a powerful tool for creating visually appealing drag-and-drop interactions.
Complex hierarchies: This is the ability to manage and manipulate structured data in a hierarchical or tree-like manner through drag-and-drop interactions. In drag-and-drop operations, complex hierarchies intuitively convey the intended changes, such as promoting or demoting items, changing parent-child relationships, and altering the order of items.
Drag-and-drop operations may seem a bit complicated from afar, but with the help of third-party libraries, we’ve successfully shown a simple yet powerful demo for dragging and dropping files and reordering those files.
We just scratched the surface of what React is capable of in terms of drag-and-drop functionality, as there are more exhaustive features you can implement using drag-and-drop libraries. Your true limit is your creativity.
I hope this helps you to build your next drag-and-drop functionality faster and with confidence. Feel free to share with us what you’ve built with it in the comments.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSimplify component interaction and dynamic theming in Vue 3 with defineExpose and for better control and flexibility.
Explore how to integrate TypeScript into a Node.js and Express application, leveraging ts-node, nodemon, and TypeScript path aliases.
es-toolkit is a lightweight, efficient JavaScript utility library, ideal as a modern Lodash alternative for smaller bundles.
The use cases for the ResizeObserver API may not be immediately obvious, so let’s take a look at a few practical examples.
6 Replies to "How to implement drag and drop in React with React DnD"
how the dropped images will be removed if we want to remove them or if we want to resize,crop,save them then how it will be performed plz help
This is a great tutorial 😀 I just have a question about the onUpdate prop in the ImageList component as it seems to come and go in your code snippets. I removed it in my code but I was wondering what it does? Also I’m having trouble with the props drilling so if you could maybe do a tutorial on that at some point in the future that would be great. Thanks again for the awesome tutorial!
You are right, I missed to remove the reference. It was later renamed to `moveImage`. That method is actually updating the state when image ordering is changed.
– Removing will be just removing from the state
– Resize, crop and save is completely a different need from drag and dropping elements. For resizing and cropping an image, you either need separate library to handle it (either in the frontend or in the backend)
– Saving an image after cropping requires backend API to save it somewhere (S3, own server storage).
This blog just demonstrate the drag and drop functionality. You can look for articles which do resize, crop and save images in server specifically. You will find many article on your favourite language or server side frameworks.
Thanks this article really helped me to solve drag&drop)
for beginners: if you strugled with this post, make sure you know how React references works, all code here is working good, so no way to say author missed something)
and DND is nice because if compare to other libs it don’t kill props population for nested childs which is great, as I used other npm package for drag&drop and it kills all which is totally wrong
Great tutorial. Works well for the use case, but did you face any problems with autoscrolling, how did you solve it?
It seems React-Dnd does not support autoscrolling when you drag an element to edges, out of the box.
also the preview generation in touch devices isn’t that controllable.