Doofus Ideas Ltd
London, UK EC1V 2NX
hello@dofusidea.com
Ph: +44.800.832.1522
For Project inquiries only:
june.harington@doofusidea.com
Ph: +44.155.657.0163
Company # 12339015
For Media & Tech Inquiries:
tech@doofusidea.com
press@doofusidea.com
Ph: +44.155.657.0163
Back

How To Use The HTML Drag-And-Drop API In React

The drag-and-drop API is one of the coolest features of HTML. It helps us implement drag-and-drop features in web browsers.

In the current context, we will be dragging files from outside the browser. On dropping the file(s), we put them on a list and display their names. With the files in hand, we could then perform some other operation on the file(s), e.g. upload them to a cloud server.

In this tutorial, we’ll be focusing on how to implement the action of dragging and dropping in a React application. If what you need is a plain JavaScript implementation, perhaps you’d first like to read “How To Make A Drag-And-Drop File Uploader With Vanilla JavaScript,” an excellent tutorial written by Joseph Zimmerman not too long ago.

The dragenterdragleavedragover, And drop Events

There are eight different drag-and-drop events. Each one fires at a different stage of the drag-and-drop operation. In this tutorial, we’ll focus on the four that are fired when an item is dropped into a drop zone: dragenterdragleavedragover and drop.

  1. The dragenter event fires when a dragged item enters a valid drop target.
  2. The dragleave event fires when a dragged item leaves a valid drop target.
  3. The dragover event fires when a dragged item is being dragged over a valid drop target. (It fires every few hundred milliseconds.)
  4. The drop event fires when an item drops on a valid drop target, i.e dragged over and released.

We can turn any HTML element into a valid drop target by defining the ondragover and ondrop event handler attributes.

Drag-And-Drop Events In React

To get started, clone the tutorial repo from this URL:

https://github.com/chidimo/react-dnd.git

Copy

Check out the 01-start branch. Make sure you have yarn installed as well. You can get it from yarnpkg.com.

But if you prefer, create a new React project and replace the content of App.js with the code below:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <h1>React drag-and-drop component</h1>
    </div>
  );
}
export default App;

Copy

Also, replace the content of App.css with the below CSS style:

.App {
  margin: 2rem;
  text-align: center;
}
h1 {
  color: #07F;
}
.drag-drop-zone {
  padding: 2rem;
  text-align: center;
  background: #07F;
  border-radius: 0.5rem;
  box-shadow: 5px 5px 10px #C0C0C0;
}
.drag-drop-zone p {
  color: #FFF;
}
.drag-drop-zone.inside-drag-area {
  opacity: 0.7;
}
.dropped-files li {
  color: #07F;
  padding: 3px;
  text-align: left;
  font-weight: bold;
}

Copy

If you cloned the repo, issue the following commands (in order) to start the app:

yarn # install dependencies
yarn start # start the app

Copy

The next step is to create a drag-and-drop component. Create a file DragAndDrop.js inside the src/ folder. Enter the following function inside the file:

import React from 'react';

const DragAndDrop = props => {
  const handleDragEnter = e => {
    e.preventDefault();
    e.stopPropagation();
  };
  const handleDragLeave = e => {
    e.preventDefault();
    e.stopPropagation();
  };
  const handleDragOver = e => {
    e.preventDefault();
    e.stopPropagation();
  };
  const handleDrop = e => {
    e.preventDefault();
    e.stopPropagation();
  };
  return (
    <div className={'drag-drop-zone'}
      onDrop={e => handleDrop(e)}
      onDragOver={e => handleDragOver(e)}
      onDragEnter={e => handleDragEnter(e)}
      onDragLeave={e => handleDragLeave(e)}
    >
      <p>Drag files here to upload</p>
    </div>
  );
};
export default DragAndDrop;

Copy

In the return div, we have defined our focus HTML event handler attributes. You can see that the only difference from pure HTML is the camel-casing.

The div is now a valid drop target since we have defined the onDragOver and onDrop event handler attributes.

We also defined functions to handle those events. Each of these handler function receives the event object as its argument.

For each of the event handlers, we call preventDefault() to stop the browser from executing its default behavior. The default browser behavior is to open the dropped file. We also call stopPropagation() to make sure that the event is not propagated from child to parent elements.

Import the DragAndDrop component into the App component and render it below the heading.

<div className="App">
  <h1>React drag-and-drop component</h1>
  <DragAndDrop />
</div>

Copy

Now view the component in the browser and you should see something like the image below.

Drop zone
div to be converted into a drop zone (Large preview)

If you’re following with the repo, the corresponding branch is 02-start-dragndrop

Managing State With The useReducer Hook

Our next step will be to write the logic for each of our event handlers. Before we do that, we have to consider how we intend to keep track of dropped files. This is where we begin to think about state management.

We will be keeping track of the following states during the drag-and-drop operation:

  1. dropDepth
    This will be an integer. We’ll use it to keep track of how many levels deep we are in the drop zone. Later on, I will explain this with an illustration. (Credits to Egor Egorov for shining a light on this one for me!)
  2. inDropZone
    This will be a boolean. We will use this to keep track of whether we’re inside the drop zone or not.
  3. FileList
    This will be a list. We’ll use it to keep track of files that have been dropped into the drop zone.

To handle states, React provides the useState and useReducer hooks. We’ll opt for the useReducer hook given that we will be dealing with situations where a state depends on the previous state.

The useReducer hook accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method.

You can read more about useReducer in the React docs.

Inside the App component (before the return statement), add the following code:

...
const reducer = (state, action) => {
  switch (action.type) {
    case 'SET_DROP_DEPTH':
      return { ...state, dropDepth: action.dropDepth }
    case 'SET_IN_DROP_ZONE':
      return { ...state, inDropZone: action.inDropZone };
    case 'ADD_FILE_TO_LIST':
      return { ...state, fileList: state.fileList.concat(action.files) };
    default:
      return state;
  }
};
const [data, dispatch] = React.useReducer(
  reducer, { dropDepth: 0, inDropZone: false, fileList: [] }
)
...

Copy

The useReducer hook accepts two arguments: a reducer and an initial state. It returns the current state and a dispatch function with which to update the state. The state is updated by dispatching an action that contains a type and an optional payload. The update made to the component’s state is dependent on what is returned from the case statement as a result of the action type. (Note here that our initial state is an object.)

For each of the state variables, we defined a corresponding case statement to update it. The update is performed by invoking the dispatch function returned by useReducer.

Now pass data and dispatch as props to the DragAndDrop component you have in your App.js file:

<DragAndDrop data={data} dispatch={dispatch} />

Copy

At the top of the DragAndDrop component, we can access both values from props.

const { data, dispatch } = props;

Copy

If you’re following with the repo, the corresponding branch is 03-define-reducers.

Let’s finish up the logic of our event handlers. Note that the ellipsis represents the two lines:

e.preventDefault()
e.stopPropagation()


const handleDragEnter = e => {
  ...
  dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth + 1 });
};

const handleDragLeave = e => {
  ...
  dispatch({ type: 'SET_DROP_DEPTH', dropDepth: data.dropDepth - 1 });
  if (data.dropDepth > 0) return
  dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false })
};

Copy

In the illustration that follows, we have nested drop zones A and B. A is our zone of interest. This is where we want to listen for drag-and-drop events.

An illustration of the ondragenter and ondragleave events
An illustration of the ondragenter and ondragleave events (Large preview)

When dragging into a drop zone, each time we hit a boundary, the ondragenter event is fired. This happens at boundaries A-in and B-in. Since we’re entering the zone, we increment dropDepth.

Likewise, when dragging out of a drop zone, each time we hit a boundary, the ondragleave event is fired. This happens at boundaries A-out and B-out. Since we’re leaving the zone, we decrement the value of dropDepth. Notice that we don’t set inDropZone to false at boundary B-out. That is why we have this line to check the dropDepth and return from the function dropDepth greater than 0.

if (data.dropDepth > 0) return

Copy

This is because even though the ondragleave event is fired, we’re still within zone A. It is only after we have hit A-out, and dropDepth is now 0 that we set inDropZone to false. At this point, we have left all drop zones.

const handleDragOver = e => {
  ...
  e.dataTransfer.dropEffect = 'copy';
  dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: true });
};

Copy

Each time this event fires, we set inDropZone to true. This tells us that we’re inside the drop zone. We also set the dropEffect on the dataTransfer object to copy. On a Mac, this has the effect of showing a green plus sign as you drag an item around in the drop zone.

const handleDrop = e => {
  ...
  let files = [...e.dataTransfer.files];
  
  if (files && files.length > 0) {
    const existingFiles = data.fileList.map(f => f.name)
    files = files.filter(f => !existingFiles.includes(f.name))
    
    dispatch({ type: 'ADD_FILE_TO_LIST', files });
    e.dataTransfer.clearData();
    dispatch({ type: 'SET_DROP_DEPTH', dropDepth: 0 });
    dispatch({ type: 'SET_IN_DROP_ZONE', inDropZone: false });
  }
};

Copy

We can access the dropped files with e.dataTransfer.files. The value is an array-like object so we use the array spread syntax to convert it to a JavaScript array.

We now need to check if there is at least one file before attempting to add it to our array of files. We also make sure to not include files that are already on our fileList. The dataTransfer object is cleared in preparation for the next drag-and-drop operation. We also reset the values of dropDepth and inDropZone.

Update the className of the div in the DragAndDrop component. This will conditionally change the className of the div depending on the value of data.inDropZone.

<div className={data.inDropZone ? 'drag-drop-zone inside-drag-area' : 'drag-drop-zone'}
      ...
    >
  <p>Drag files here to upload</p>
</div>

Copy

Render the list of files in App.js by mapping through data.fileList.

<div className="App">
  <h1>React drag-and-drop component</h1>
  <DragAndDrop data={data} dispatch={dispatch} />
  <ol className="dropped-files">
    {data.fileList.map(f => {
      return (
        <li key={f.name}>{f.name}</li>
      )
    })}
  </ol>
</div>

Copy

Now try dragging and dropping some files onto the drop zone. You’ll see that as we enter the drop zone, the background becomes less opaque because the inside-drag-area class is activated.

When you release the files inside the drop zone, you’ll see the file names listed under the drop zone:

Drop zone showing low opacity during dragover
Drop zone showing low opacity during dragover (Large preview)
A list of files dropped into the drop zone
A list of files dropped into the drop zone (Large preview)

The complete version of this tutorial is on the 04-finish-handlers branch.

Conclusion

We’ve seen how to handle file uploads in React using the HTML drag-and-drop API. We’ve also learned how to manage state with the useReducer hook. We could extend the file handleDrop function. For example, we could add another check to limit file sizes if we wanted. This can come before or after the check for existing files. We could also make the drop zone clickable without affecting the drag-and-drop functionality.

Author avatar
doofus

Leave a Reply

Your email address will not be published. Required fields are marked *

Doofus Ideas Ltd
Privacy Overview

What is this Privacy Policy for?
This privacy policy is for this website [http://doofusidea.com] and served by Doofus ideas the privacy of its users who choose to use it.

The policy sets out the different areas where user privacy is concerned and outlines the obligations & requirements of the users, the website and website owners. Furthermore, the way this website processes, stores and protects user data and information will also be detailed within this policy.

The Website
This website and its owners take a proactive approach to user privacy and ensure the necessary steps are taken to protect the privacy of its users throughout their visiting experience. This website complies with all UK national laws and requirements for user privacy.

What information do we collect?
We may collect the following personal information:

Your IP address (in server logs)
Your name (where it is provided to us in the contact forms on this website)
Your email address (where it is provided to us in the contact forms on this website)
Your phone number (where it is provided to us in the contact forms on this website)
What do we use this information for?
Your IP address may be used for diagnostic and forensic reasons on our server or for the purposes of identifying your business name when browsing the site from a corporate network location.

We will use your name and other contact details for the purposes of answering inquiries.

How long will we keep your data?
Your IP address information will be retained in server logs for 30 days and then deleted.
Email address and contact information will be retained for 7 years following completion of any contractual engagement.
Contact information collected for newsletter purposes will be retained until removal is requested by the data subject.
How can I control the use of my data?
You have a wide range of rights as regards your personal data. Under current data protection laws, you have the right to request the following:

The right to the erasure of your data (the right to be forgotten).
The right to object (the right to have us stop using your data for a specified purpose)
The right to amendment (the right to have incorrect data amended)
The right to access (to be given a copy of all the data we hold on you in a portable format)
The right to the restriction of processing (to restrict the use of your data)
Rights related to automated processing (we do not conduct any automated processing)
If you have any questions at all about your data or Doofus Ideas data protection, please use the following contact information:
Email: hello@doofusidea.com

Phone: +44 8008321522

You are also entitled to raise a complaint with the ICO whose details can be found here: www.ico.org

How do we protect your data?
As an accredited organization, we already have the foundations of compliance pre-built as the technical and organizational measures required under this ISO accreditation are a good match to meet, and exceed, the test of “reasonable technical and organizational measures” required under the regulation. We are also registered with the ICO as a controller and processor under the terms of the DPA (Data Protection Act).

All our staff receives training on information security and data protection and we take this responsibility very seriously as an organization.

Use of Cookies
This website uses cookies to better the users experience while visiting the website. Where applicable this website uses a cookie control system allowing the user on their first visit to the website to allow or disallow the use of cookies on their computer/device. This complies with recent legislation requirements for websites to obtain explicit consent from users before leaving behind or reading files such as cookies on a user’s computer/device.

Cookies are small files saved to the user’s computer’s hard drive that track, save and store information about the user’s interactions and usage of the website. This allows the website, through its server to provide the users with a tailored experience within this website.

Users are advised that if they wish to deny the use and saving of cookies from this website on to their computer’s hard drive they should take necessary steps within their web browsers security settings to block all cookies from this website and its external serving vendors.

This website uses tracking software to monitor its visitors to better understand how they use it. This software is provided by Google Analytics which uses cookies to track visitor usage. The software will save a cookie to your computer’s hard drive in order to track and monitor your engagement and usage of the website, but will not store, save or collect personal information. You can read Google’s privacy policy here for further information [http://www.google.com/privacy.html ].

Other cookies may be stored to your computer’s hard drive by external vendors when this website uses referral programs, sponsored links or adverts. Such cookies are used for conversion and referral tracking and typically expire after 30 days, though some may take longer. No personal information is stored, saved or collected.

Contact & Communication
Users contacting this website and/or its owners do so at their own discretion and provide any such personal details requested at their own risk. Your personal information is kept private and stored securely until a time it is no longer required or has no use, as detailed in the Data Protection Act 1998. Every effort has been made to ensure a safe and secure form to email the submission process but advise users using such form to email processes that they do so at their own risk.

This website and its owners use any information submitted to provide you with further information about the products/services they offer or to assist you in answering any questions or queries you may have submitted. This includes using your details to subscribe to you to any email newsletter program the website operates but only if this was made clear to you and your express permission was granted when submitting any form to email process. Or whereby you the consumer have previously purchased from or enquired about purchasing from the company a product or service that the email newsletter relates to. This is by no means an entire list of your user rights in regard to receiving email marketing material. Your details are not passed on to any third parties.

Email Newsletter
This website operates an email newsletter program, used to inform subscribers about products and services supplied by this website. Users can subscribe through an online automated process should they wish to do so but do so at their own discretion. Some subscriptions may be manually processed through a prior written agreement with the user.

Subscriptions are taken in compliance with UK Spam Laws detailed in the Privacy and Electronic Communications Regulations 2003. All personal details relating to subscriptions are held securely and in accordance with the Data Protection Act 1998. No personal details are passed on to third parties nor shared with companies/people outside of the company that operates this website. Under the Data Protection Act 1998, you may request a copy of personal information held about you by this website’s email newsletter program. A small fee will be payable. If you would like a copy of the information held on you please write to the business address at the bottom of this policy.

Email marketing campaigns published by this website or its owners may contain tracking facilities within the actual email. Subscriber activity is tracked and stored in a database for future analysis and evaluation. Such tracked activity may include; the opening of emails, forwarding of emails, the clicking of links within the email content, times, dates, and frequency of activity [this is by no far a comprehensive list].

This information is used to refine future email campaigns and supply the user with more relevant content based on their activity.

In compliance with UK Spam Laws and the Privacy and Electronic Communications Regulations 2003 subscribers are given the opportunity to un-subscribe at any time through an automated system. This process is detailed at the footer of each email campaign. If an automated un-subscription system is unavailable clear instructions on how to un-subscribe will by detailed instead.

External Links
Although this website only looks to include quality, safe and relevant external links, users are advised to adopt a policy of caution before clicking any external web links mentioned throughout this website.

The owners of this website cannot guarantee or verify the contents of any externally linked website despite their best efforts. Users should, therefore, note they click on external links at their own risk and this website and its owners cannot be held liable for any damages or implications caused by visiting any external links mentioned.

Adverts and Sponsored Links
This website may contain sponsored links and adverts. These will typically be served through our advertising partners, to whom may have detailed privacy policies relating directly to the adverts they serve.

Clicking on any such adverts will send you to the advertiser’s website through a referral program which may use cookies and will track the number of referrals sent from this website. This may include the use of cookies which may, in turn, be saved on your computer’s hard drive. Users should, therefore, note they click on sponsored external links at their own risk and this website and its owners cannot be held liable for any damages or implications caused by visiting any external links mentioned.

Social Media Platforms
Communication, engagement, and actions taken through external social media platforms that this website and its owners participate in are custom to the terms and conditions as well as the privacy policies held with each social media platform respectively.

Users are advised to use social media platforms wisely and communicate/engage upon them with due care and caution in regard to their own privacy and personal details. This website nor its owners will ever ask for personal or sensitive information through social media platforms and encourage users wishing to discuss sensitive details to contact them through primary communication channels such as by telephone or email.

This website may use social sharing buttons that help share web content directly from web pages to the social media platform in question. Users are advised before using such social sharing buttons that they do so at their own discretion and note that the social media platform may track and save your request to share a web page respectively through your social media platform account.

Please do not hesitate in contacting us regarding this Privacy Policy at hello@doofusidea.com