React Drag and Drop File Upload example

Tien Nguyen - Dec 13 '22 - - Dev Community

In this React tutorial, I will show you way to build Drag and Drop File Upload example with React Hooks using react-dropzone, Axios and Multipart File for making HTTP requests, Bootstrap for progress bar and display list of files' information (with download url).

From BezKoder.

Overview

We’re gonna create a Drag and Drop File Upload by React example in that user can:

  • drag file and drop it into Drop zone
  • see the upload process (percentage) with progress bar
  • view all uploaded files
  • download link to file when clicking on the file name

react-drag-and-drop-file-upload-example

Right after drag and drop file into the Dropzone:

react-drag-and-drop-file-upload-dropzone

Click on Upload button:

react-drag-and-drop-file-upload-progress-bar

Technology

  • React 18/17/16
  • Axios
  • react-dropzone 11.4.0
  • Bootstrap 4

Rest API for File Upload & Storage

Here is the API that our React App will work with:

Methods Urls Actions
POST /upload upload a File
GET /files get List of Files (name & url)
GET /files/[filename] download a File

You can find how to implement the Rest APIs Server at one of following posts:

Or: Spring Boot Multipart File upload (to database) example

React Drag and Drop File Upload Application

After building the React.js project is done, the folder structure will look like this:

react-drag-and-drop-file-upload-project-structure

Let me explain it briefly.

  • FileUploadService provides functions to save File and get Files using Axios.
  • FileUpload contains file upload dropzone, progress bar, display of list files.
  • App.js is the container that we embed all React components.

  • http-common.js initializes Axios with HTTP base Url and headers.

  • We configure port for our App in .env

Setup Drag and Drop File Upload Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app drag-drop-file-upload-react-hooks

After the process is done. We create additional folders and files like the following tree:


public
src
--components
----FileUpload.js
--services
----FileUploadService.js
--App.css
--App.js
--index.js
package.json

Import Bootstrap to React Drag Drop File Upload App

Run command: yarn add bootstrap@4.6.0
Or: npm install bootstrap@4.6.0.

Open src/App.js and modify the code inside it as following-

import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";

function App() {
  return (
    ...
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Initialize Axios for React HTTP Client

Let's install axios with command:
yarn add axios or npm install axios.

Under src folder, we create http-common.js file with following code:

import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:8080",
  headers: {
    "Content-type": "application/json"
  }
});
Enter fullscreen mode Exit fullscreen mode

You can change the baseURL that depends on REST APIs url that your Server configures.

Create Service for File Upload

This service will use Axios to send HTTP requests.
There are 2 functions:

  • uploadFile(file): POST form data with a callback for tracking upload progress
  • getFiles(): GET list of Files' information

services/FileUploadService.js

import http from "../http-common";

export const uploadFile = (file, onUploadProgress) => {
  let formData = new FormData();

  formData.append("file", file);

  return http.post("/upload", formData, {
    headers: {
      "Content-Type": "multipart/form-data",
    },
    onUploadProgress,
  });
};

export const getFiles = () => {
  return http.get("/files");
};
Enter fullscreen mode Exit fullscreen mode
  • First we import Axios as http from http-common.js.

  • Inside uploadFile() method, we use FormData to store key-value pairs. It helps to build an object which corresponds to HTML form using append() method.

  • We pass onUploadProgress to exposes progress events. This progress event are expensive (change detection for each event), so you should only use when you want to monitor it.

  • We call Axios post() to send an HTTP POST for uploading a File to Rest APIs Server and get() method for HTTP GET request to retrieve all stored files.

Install react-dropzone

Add react-dropzone module into project with command:

  • yarn add react-dropzone
  • Or: npm install react-dropzone

Create Page for Upload Files

Let's create a File Upload UI with Progress Bar, Card, Button and Message.

First we create a React template with React Hooks (useState, useEffect) and import react-dropzone, FileUploadService functions:

components/FileUpload.js

import React, { useState, useEffect } from "react";
import Dropzone from "react-dropzone";
import { getFiles, uploadFile } from "../services/FileUploadService";

const UploadFiles = () => {

  return (

  );
};

export default UploadFiles;
Enter fullscreen mode Exit fullscreen mode

Then we define the state using React Hooks:

const UploadFiles = () => {

  const [selectedFiles, setSelectedFiles] = useState(undefined);
  const [currentFile, setCurrentFile] = useState(undefined);
  const [progress, setProgress] = useState(0);
  const [message, setMessage] = useState("");

  const [fileInfos, setFileInfos] = useState([]);

  ...
}
Enter fullscreen mode Exit fullscreen mode

Next we define onDrop() method which helps us to get the selected Files from element later.

const UploadFiles = () => {
  ...
  const onDrop = (files) => {
    if (files.length > 0) {
      setSelectedFiles(files);
    }
  };

  ...
}
Enter fullscreen mode Exit fullscreen mode

We use selectedFiles for accessing current File as the first Item. Then we call UploadService.uploadFile() method on the currentFile with a callback. So create following upload() method:

const UploadFiles = () => {
  ...
  const upload = () => {
    let currentFile = selectedFiles[0];

    setProgress(0);
    setCurrentFile(currentFile);

    uploadFile(currentFile, (event) => {
      setProgress(Math.round((100 * event.loaded) / event.total));
    })
      .then((response) => {
        setMessage(response.data.message);
        return getFiles();
      })
      .then((files) => {
        setFileInfos(files.data);
      })
      .catch(() => {
        setProgress(0);
        setMessage("Could not upload the file!");
        setCurrentFile(undefined);
      });

    setSelectedFiles(undefined);
  };
  ...
}
Enter fullscreen mode Exit fullscreen mode

The progress will be calculated basing on event.loaded and event.total.
If the transmission is done, we call UploadService.getFiles() to get the files' information and assign the result to fileInfos state, which is an array of {name, url} objects.

We also need to do this work in the Effect Hook useEffect() method which serves the same purpose as componentDidMount():

const UploadFiles = () => {
  ...
  useEffect(() => {
    getFiles().then((response) => {
      setFileInfos(response.data);
    });
  }, []);

  ...
}
Enter fullscreen mode Exit fullscreen mode

Now we return the Upload File UI. Add the following code inside return() block:

const UploadFiles = () => {
  ...
  return (
    <div>
      {currentFile && (
        <div className="progress mb-3">
          <div
            className="progress-bar progress-bar-info progress-bar-striped"
            role="progressbar"
            aria-valuenow={progress}
            aria-valuemin="0"
            aria-valuemax="100"
            style={{ width: progress + "%" }}
          >
            {progress}%
          </div>
        </div>
      )}

      <Dropzone onDrop={onDrop} multiple={false}>
        {({ getRootProps, getInputProps }) => (
          <section>
            <div {...getRootProps({ className: "dropzone" })}>
              <input {...getInputProps()} />
              {selectedFiles && selectedFiles[0].name ? (
                <div className="selected-file">
                  {selectedFiles && selectedFiles[0].name}
                </div>
              ) : (
                "Drag and drop file here, or click to select file"
              )}
            </div>
            <aside className="selected-file-wrapper">
              <button
                className="btn btn-success"
                disabled={!selectedFiles}
                onClick={upload}
              >
                Upload
              </button>
            </aside>
          </section>
        )}
      </Dropzone>

      <div className="alert alert-light" role="alert">
        {message}
      </div>

      {fileInfos.length > 0 && (
        <div className="card">
          <div className="card-header">List of Files</div>
          <ul className="list-group list-group-flush">
            {fileInfos.map((file, index) => (
              <li className="list-group-item" key={index}>
                <a href={file.url}>{file.name}</a>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the code above, we use Bootstrap Progress Bar:

  • .progress as a wrapper
  • inner .progress-bar to indicate the progress
  • .progress-bar requires style to set the width by percentage
  • .progress-bar also requires role and some aria attributes to make it accessible
  • label of the progress bar is the text within it

To display List of uploaded files, we iterate over fileInfos array using map() function. On each file item, we use file.url as href attribute and file.name for showing text.

Don't forget to export the function component:

const UploadFiles = () => {
  ...
}

export default UploadFiles;
Enter fullscreen mode Exit fullscreen mode

CSS style for Dropzone and File

Open App.css and add following styles:

.dropzone {
  text-align: center;
  padding: 30px;
  border: 3px dashed #eeeeee;
  background-color: #fafafa;
  color: #bdbdbd;
  cursor: pointer;
  margin-bottom: 20px;
}

.selected-file-wrapper {
  text-align: center;
}

.selected-file {
  color: #000; 
  font-weight: bold;
}
Enter fullscreen mode Exit fullscreen mode

Add Drag and Drop File Upload Component to App Component

Open App.js, import and embed the UploadFiles Component tag.

import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";

import FileUpload from "./components/FileUpload";

function App() {
  return (
    <div className="container" style={{ width: "600px" }}>
      <div className="my-3">
        <h3>bezkoder.com</h3>
        <h4>React Drag & Drop File Upload</h4>
      </div>

      <FileUpload />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Configure Port for React App

Because most of HTTP Server use CORS configuration that accepts resource sharing restricted to some sites or ports, you need to configure port for our App.

In project folder, create .env file with following content:

PORT=8081
Enter fullscreen mode Exit fullscreen mode

So our app will run at port 8081.

Run the App

You can find how to implement the Rest APIs Server at one of following posts:

Run this React Client with command: yarn start or npm run start.

Open Browser with url http://localhost:8081/ and check the result.

Conclusion

Today we're learned how to build a React Hooks application for Drap and Drop file upload using React-Dropzone, Axios, Bootstrap with Progress Bar. We also provide the ability to show list of files, upload progress percentage, and to download file from the server.

For Multiple Files Upload like this:

react-hooks-multiple-files-upload-example

Please visit:
React Hooks Multiple File upload example with Axios & Progress Bar

Using React Components instead:
React Drag and Drop File Upload example

Happy Learning! See you again.

Further Reading

More Practice:

Fullstack:

Serverless:

Source Code

You can find the complete source code for this tutorial at Github.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .