Editor’s note: This tutorial was last updated on 17 March 2022 to reflect updates to React DnD.
The Drag and Drop API brings draggable elements to HTML, enabling developers to build applications that contain rich UI elements that can be dragged from one place to another. The Drag and Drop API 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. As a demonstration, we’ll create a simple application based on this example.
You can follow along with this tutorial by accessing the full codebase for this project and checking out the demo. Let’s get started!
Table of contents
- How to implement drag and drop in React
- Uploading files using drag and drop in React
- Display an image preview
- Reorder images with drag and drop
- React drag and drop libraries
- react-beautiful-dnd
- React-Grid-Layout
- React DnD
- Exploring React DnD
- React Native drag and drop
- Conclusion
How to implement drag and drop in React
For our example, we’ll build a simple application that enables users to upload image files by dropping them in the browser, displaying a preview of those images as a grid, and reordering the images.
First, we’ll bootstrap a React app using Create React App:
npx create-react-app logrocket-drag-and-drop cd logrocket-drag-and-drop yarn start
If you’re using npm, simply replace yarn start
with npm start
.
If you prefer a visual tutorial, check out our react-beautiful-dnd walkthrough below:
Uploading files using drag and drop in React
We won’t reinvent the wheel by creating all the logic and components on our own. Instead, we’ll use some of the most common React drag and drop libraries available, starting with react-dropzone.
With nearly 8,000 stars on GitHub at the time of writing, react-dropzone is a very powerful library that helps you create custom components in React and is up to date with React Hooks support. To install react-dropzone, run either of the commands below:
yarn add react-dropzone #or npm install react-dropzone
Next, create a new file called Dropzone.js
, which is responsible for making a simple content area into a dropzone area where you can drop your files.
What is react-dropzone
?
react-dropzone
hides the file input and shows the beautiful custom dropzone area. When we drop the files, react-dropzone
uses HTML onDrag
events and captures the files from the event based on whether the files are dropped inside the dropzone area.
If we click on the area, the react-dropzone
library initiates file selection dialog through the hidden input using React ref
, allowing us to select and upload files. Let’s create our Dropzone
component:
/* filename: Dropzone.js */ import React from "react"; // Import the useDropzone hooks from react-dropzone import { useDropzone } from "react-dropzone"; const Dropzone = ({ onDrop, accept }) => { // Initializing useDropzone hooks with options const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept }); /* useDropzone hooks exposes two functions called getRootProps and getInputProps and also exposes isDragActive boolean */ return ( <div className="dropzone-div" {...getRootProps()}> <input className="dropzone-input" {...getInputProps()} /> <div className="text-center"> {isDragActive ? ( <p className="dropzone-content">Release to drop the files here</p> ) : ( <p className="dropzone-content"> Drag 'n' drop some files here, or click to select files </p> )} </div> </div> ); }; export default Dropzone;
The component is straightforward, but let’s take a closer look at the code. useDropzone
exposes several methods and variables for us to create the custom dropzone area. For our project, we are mostly interested in three properties.
For one, getRootProps
is set based on the parent element of the dropzone area. This element determines the width and height of the dropzone area. getInputProps
is the prop passed to the input element. It enables us to support click events along with drag events to get files.
All the options related to files we pass to the useDropzone
are set to this input element. For example, if you want to support only single files, you can pass multiple: false
. It will automatically require the dropzone
to allow only one file to get accepted.
isDragActive
is set if the files are dragged above the dropzone area, which will be very useful to make the styling based on this variable.
The example below demonstrates how to set the styles/class names based on the isDragActive
value:
const getClassName = (className, isActive) => { if (!isActive) return className; return `${className} ${className}-active`; }; ... <div className={getClassName("dropzone", isDragActive)} {...getRootProps()}> ...
In our drag and drop example, we used only two props, however, the library supports a lot of props to customize the dropzone area based on your needs.
To only accept image files, we used accept
props. Now, our App.js
should look like the following code:
/* filename: App.js */ import React, { useCallback } from "react"; // Import the dropzone component import Dropzone from "./Dropzone"; import "./App.css"; function App() { // onDrop function const onDrop = useCallback(acceptedFiles => { // this callback will be called after files get dropped, we will get the acceptedFiles. If you want, you can even access the rejected files too console.log(acceptedFiles); }, []); // We pass onDrop function and accept prop to the component. It will be used as initial params for useDropzone hook return ( <main className="App"> <h1 className="text-center">Drag and Drop Example</h1> <Dropzone onDrop={onDrop} accept={"image/*"} /> </main> ); } export default App;
We added the dropzone
component in the main page. Now, if you drop the files, it will console the dropped image files.
Keep in mind that the image above has some styling applied to the dropzone div, like padding and a border. Yours may appear differently until you provide some styles. Adding the following code in index.css
should give you a similar appearance:
body { text-align: center; } .dropzone-div { text-align: center; padding: 20px; border: 3px purple dashed; width: 60%; margin: auto; }
acceptedFiles
is an array of File
values. You can read the file or send it to the server and upload. Whatever process you want to do, you can do it there. Even when you click the area and upload, the same onDrop
callback is called.
The accept
prop accepts mime types. It supports all standard mime types and match patterns. If you want to allow only PDFs, for example, then accept={'application/pdf'}
. If you want both images and PDFs, it supports accept={'application/pdf, image/*'}
.
The onDrop
function is enclosed in a useCallback
. As of now, we didn’t do any heavy computing or send the files to the server. We just console the acceptedFiles
. Later on, we’ll read the files and set a state for displaying the images in the browser. It’s recommended to use useCallback
for expensive functions to avoid unnecessary re-renders, however, in our example, it’s completely optional.
Let’s read the image files and add them to a state in App.js
. Also, be sure to install the cuid
library with either yarn add cuid
or npm install cuid
:
/* filename: App.js */ import React, { useCallback, useState } from "react"; // cuid is a simple library to generate unique IDs import cuid from "cuid"; function App() { // Create a state called images using useState hooks and pass the initial value as empty array const [images, setImages] = useState([]); const onDrop = useCallback(acceptedFiles => { // Loop through accepted files acceptedFiles.map(file => { // Initialize FileReader browser API const reader = new FileReader(); // onload callback gets called after the reader reads the file data reader.onload = function(e) { // add the image into the state. Since FileReader reading process is asynchronous, its better to get the latest snapshot state (i.e., prevState) and update it. setImages(prevState => [ ...prevState, { id: cuid(), src: e.target.result } ]); }; // Read the file as Data URL (since we accept only images) reader.readAsDataURL(file); return file; }); }, []); ... }
The data structure of our images
state is below:
const images = [ { id: 'abcd123', src: 'data:image/png;dkjds...', }, { id: 'zxy123456', src: 'data:image/png;sldklskd...', } ]
Display an image preview
To show the image preview in a grid layout, we’ll create another component called ImageList
:
import React from "react"; // Rendering individual images const Image = ({ image }) => { return ( <div className="file-item"> <img alt={`img - ${image.id}`} src={image.src} className="file-img" /> </div> ); }; // ImageList Component const ImageList = ({ images }) => { // render each image by calling Image component const renderImage = (image, index) => { return ( <Image image={image} key={`${image.id}-image`} /> ); }; // Return the list of files return <section className="file-list">{images.map(renderImage)}</section>; }; export default ImageList;
Now, we can add this ImageList
component to App.js
to show the preview of the images:
function App() { ... // Pass the images state to the ImageList component and the component will render the images return ( <main className="App"> <h1 className="text-center">Drag and Drop Example</h1> <Dropzone onDrop={onDrop} accept={"image/*"} /> <ImageList images={images} /> </main> ); }
At this point, we’ve successfully completed half of our application. We can now drag and drop to see a preview of the images:
Reorder images with drag and drop
Next, we’ll allow the user to reorder the images by dragging and dropping. But first, let’s quickly review the most popular libraries used to implement this functionality and walk you through choosing the best one based on your project’s needs.
The image above has styling applied. The images appear horizontally using a flexbox on the container where the images will be displayed. Putting the following in your index.css
should give approximately the same appearance:
.file-list { display: flex; flex-wrap: wrap; width: 65%; margin: 20px auto; padding: 10px; border: 3px dotted black; } .file-list img { height: 300px; width: 300px; object-fit: cover; }
React drag-and-drop libraries
Three of the most popular React drag-and-drop packages are:
Each library is popular among React developers and has active contributors. Let’s quickly zoom in on each library and break down the pros and cons.
react-beautiful-dnd
react-beautiful-dnd is a higher-level abstraction specifically built for lists. It is designed to deliver a natural, beautiful, and accessible React drag and drop experience.
react-beautiful-dnd pros
react-beautiful-dnd works really well for one-dimensional layouts and drag and drop features that require either horizontal or vertical movement. For example, a Trello layout would work out of the box with react-beautiful-dnd.
The react-beautiful-dnd API is a breeze. The team managed to create a really enjoyable developer experience without adding complexity to the codebase
react-beautiful-dnd cons
react-beautiful-dnd doesn’t work for grids because you move elements in all directions. react-beautiful-dnd won’t be able to calculate the positions for x-axis and y-axis at the same time. So, while dragging the elements on the grid, your content will be displaced randomly until you drop the element.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Advisory boards aren’t just for executives. 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.
React-Grid-Layout
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. It does not require jQuery.
React-Grid-Layout pros
React-Grid-Layout works well for complex grid layouts that require drag and drop, like dashboards that have complete customization and resizing, i.e.,looker, data visualization products, etc.
It’s worth the complexity for large-scale application needs.
React-Grid-Layout cons
In my opinion, React-Grid-Layout provides an unattractive API that also requires you to perform a lot of calculations on your own. The layout structure has to be defined in the UI through React-Grid-Layout’s component API, which introduces an additional level of complexity when you create dynamic elements on the fly.
React DnD
React DnD is a set of React utilities designed to help you build advanced drag-and-drop interfaces while keeping your components decoupled. It enables functionalities similar to apps like Trello and Storify where data is transferred between various parts of the app and components change their appearance and application state in response to drag and drop events.
React DnD pros
React DnD works for almost all use cases, like grids, one-dimensional lists, etc. Additionally, React DnD has a very powerful API to add any customization to drag and drop in React.
React DnD cons
For small examples, the API is very easy to get started with. However, it can be very tricky to add complex customizations. The learning curve is higher and more complex than react-beautiful-dnd. Additionally, some hacks are required to support both web and touch devices
For our use case, I chose to use React DnD. I would’ve chosen react-beautiful-dnd if the layout involved only a list of items, but in our example, we have an image grid. Therefore, the easiest API for achieving drag and drop is React DnD.
Exploring 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 has a few assumptions:
- React DnD needs to have the references of all droppable items
- React DnD needs to have the references of all draggable items
All elements that are draggable and droppable need to be enclosed inside React DnD’s context provider, which is used for initializing and also managing the internal state.
We don’t need to worry too much about how it manages state; React DnD includes nice and easy APIs to expose those states, enabling us to compute and update our local states.
To install React DnD, 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 of the final product Pre react-dnd v14 and Post react-dnd v14 for reference.
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> ); }
Just import the DNDProvider
and initialize it with the backend
props. As I mentioned previously, this is the variable that helps us chose which API to use for drag and drop.
- HTML5 Drag and Drop API is supported only on the web, not on touch devices
- The touch drag-and-drop API is supported on touch devices
Currently, we use HTML5 API to get started. Once the functionality is done, we’ll write a simple utility to provide basic support for touch devices as well.
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 already draggable. But, if we drop it, the image will go to its original position because useDrag
and useDrop
will handle it until we drop it. Unless we change our local state, it will once again go back 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 also 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. The 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 nice 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.
Therefore, we 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. You can check that out in our demo page.
So far, we just show the code for updating the state as moveImage
. 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 still 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 Native drag and drop
For more information on how to implement drag and drop in React Native, check out the video tutorial below:
Conclusion
We’ve successfully built a small and powerful demo for dragging and dropping files, uploading files, and reordering those files.
We just scratched the surface of what React is capable of in terms of drag and drop functionality. We can build very exhaustive features using drag and drop libraries. We discussed some of the best libraries available at the time of writing.
I hope it helps you to build your next drag-and-drop functionality faster and with confidence. Be sure show me what you’ve built with it in the comments.
LogRocket: Full visibility into your production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?
Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.
No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.
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.