Skip to content

Implement Drag and Drop for Media Upload in React

Media uploading has transitioned from conventional clicking and uploading to drag and drop functionality. Most companies are now moving to this new trend for a better user experience. In this tutorial, we will be implementing a drag and drop functionality in a react application.

First, we need to create our react application. To do that, Make sure you have Nodejs and npm installed then paste the code below into the terminal.

npx create-react-app media-upload

Install the react-dropzone package, which will help us implement the drag and drop functionality. To install the package, run the code below.

npm install react-dropzone

After setting up our project and installing the react-dropzone package, the next is to import the package into our App.js. We will be importing the useDropzone hook from the package and calling it inside our component. To do that, paste the code snippet below.

import React from 'react';
import { useDropzone } from 'react-dropzone';
    
function App() {
const {
  getRootProps,
  getInputProps,
  isDragActive,
} = useDropzone({ onDrop });
    
return (
       ...
);
}
export default App;
Code language: JavaScript (javascript)

useDropZone hook gives us access to the properties for handling dropping a file. It also takes a config object, and one of the properties in the config object is the onDrop function called after the file is dropped in the dropzone.

useDropZone returns getRootProps, getInputProps, isDragActive which we can call to get the Drag and Drop API properties and pass those to our elements.

This is what the dropzone input and root should look like:

<div {...getRootProps()}>
  <input {...getInputProps()} />
    <!-- Any content you want to show on the drop zone -->
</div>
Code language: HTML, XML (xml)

The getRootProps sets drag and drop API properties on the chosen root. Here is what the properties look like:

These events allow your browser to listen to users’ drag and drop interactions.

The getInputProps is like getRootProps but attaches events and properties to an input. It will do things like setting the input type to file and also attaching the onChange event handler. Here are the properties:

The isDragActive is a boolean value that is true if we are dragging a file over the Dropzone but false if not. To add the isDragActive function paste the code below in after the input tag.

{isDragActive ? (
  <p className="text-center text-xl">Drop your media files here</p>
) : (
  <p className="text-center text-xl">
    Drag and drop some files here, or click to select files
  </p>
)}
Code language: HTML, XML (xml)

Now that we have added the useDropZone properties to our JSX. we will define our callback function. Remember we mentioned that onDrop is the callback function called when a file is dropped on the drop zone. Paste the code below to define the function.

const onDrop = React.useCallback((acceptedFiles) => {
  console.log(acceptedFiles);
}, []);
    
const {
  getRootProps,
  getInputProps,
  isDragActive,
} = useDropzone({ onDrop });
Code language: JavaScript (javascript)

The onDrop function passed to the hook receives an argument containing the information about the updated file. Here, we can upload the file to a cloud, preview the file, and more. Wrapped the function with useCallback to ensure that it does not trigger unnecessary re-renders.

Suppose we want to set props on elements with either the getRootProps or getInputProps. In that case, we need to put them as an argument to those functions to avoid overriding things unexpectedly.

So instead of this:

<div {...getRootProps()} role="button">
  <input {...getInputProps()} />
    <!-- Any content you want to show on the drop zone -->
</div>
Code language: HTML, XML (xml)

Do this:

<div {...getRootProps({
  role: 'button'
})}>
  <input {...getInputProps()} />
    <!-- Any content you want to show on the drop zone -->
</div>
Code language: HTML, XML (xml)

The useDropzone has some predefined props for validations. It has the accept props, maxFiles props, and many others. It can also accept a defined validation function as props if we choose not to use the predefined functions. The accept props disallows files not included in the accept list. Hence we can only select files that are listed. In the snippet below, we can only upload png and jpeg files.

useDropzone({
  accept: 'image/jpeg,image/png'
});
Code language: CSS (css)

maxFiles defines the maximum number of files that can be uploaded. If the validation fails, we will get an empty array in the onDrop callback.

useDropzone({    
  maxFiles: 2
});
Code language: CSS (css)

To show the actual error, we can inspect the fileRejections from useDropzone.

const {
  fileRejections, // Error files
  getRootProps,
  getInputProps,
  isDragActive,
} = useDropzone({
  onDrop,
  accept: 'image/jpeg,image/png',
  maxFiles: 2,
});
Code language: JavaScript (javascript)

To display the error message using the fileRejections ,You can iterate through the rejected files and render the errors.

Paste the code below to display the errors:

{fileRejections.map(({ file, errors }) => {
  return (
    <li key={file.path}>
      {file.path} - {file.size} bytes
      <ul>
        {errors.map((e) => (
          <li key={e.code}>{e.message}</li>
        ))}
      </ul>
    </li>
  );
})}
Code language: JavaScript (javascript)

We can also do all sorts of flexible and custom validations and not just limited to what is allowed as an argument to the useDropzone hook. Here is an example showing how to validate for max size for each file:

// Validate that the file size is less than 2mb

function fileSizeValidator(file) {
  if (file.size > 1024 ** 2 * 2) {
    return {
      code: 'size-too-large',
      message: `File is larger than 2mb`,
    };
  }
  return null;
}
    
const onDrop = React.useCallback((acceptedFiles) => {
  console.log(acceptedFiles);
}, []);
    
const {
  fileRejections,
  getRootProps,
  getInputProps,
  isDragActive,
} = useDropzone({
  onDrop,
  // Pass validator to useDropzone
  validator: fileSizeValidator,
});
Code language: JavaScript (javascript)

In the code snippet above, we wrote a custom validation function, fileSizeValidator, that validates the maximum size of each file upload. We then added it as the validator value in the useDropzone hook.

To preview images, use URL.createObjectURL and pass the file object as an argument to it. Before we do that, create a state to store the file object:

const [files, setFiles] = React.useState([]);
Code language: JavaScript (javascript)

In the onDrop function, use the createObjectURL to generate a preview and attach the preview to the file object:

const onDrop = React.useCallback((acceptedFiles) => {
  console.log(acceptedFiles);
  setFiles(
    acceptedFiles.map((file) =>
      Object.assign(file, {
        preview: URL.createObjectURL(file),
      })
    )
  );
}, []);
Code language: JavaScript (javascript)

Finally loop through the files and display a preview using an image tag if they are images:

{files.map(f => {
  return (<img src={f.preview} />)
})}
Code language: JavaScript (javascript)

You can upload the files directly from the browser using Cloudinary Unsigned Upload. Add a button to upload whatever you have in the state:

// If files are in the state, upload the first item in the array of files

{files.length > 0 && (
  <button
    onClick={() => {
    const url =
      'https://api.cloudinary.com/v1_1/codebeast/image/upload';
    const formData = new FormData();
      // Use the first item to upload
      let file = files[0];
      formData.append('file', file);
      formData.append('upload_preset', 's9n5tgkf');
      fetch(url, {
        method: 'POST',
        body: formData,
      })
        .then((response) => {
          return response.json();
        })
        .then((data) => {
          console.log(data);
        });
    }}
  >
    Upload
</button>
)}
Code language: PHP (php)

Implementing such a fantastic user experience feature in our React application should now be a thing of the past. This tutorial covered almost all critical areas from validations, upload, and error handling. I hope this tutorial is helpful. Don’t forget to check the official documentation of React Dropzone to explore further and add more features to your app. The complete source code for this tutorial is available on Codesandbox.

Back to top

Featured Post