Multiple File Upload in React with Progress Bar

by Vincy. Last modified on February 11th, 2024.

The multi-file upload is a feature that allows users to perform a bulk file upload. It saves users the effort of picking each file one by one.

In a previous tutorial, we have seen how to create a React component for a single file upload. In this tutorial, we support multi-file upload by processing an array of files uploaded by users.

View demo

This example shows a progress bar to show the uploading progress. Since the multi-file upload takes a significant time, this progress bar will be helpful to know the status.

multiple file upload react

Steps to multiple file upload using React

  1. Create multiple files upload React component and render to the UI.
  2. Bind UI controls with the JS handlers to access files and trigger the upload.
  3. Acknowledge users with a progress bar and previews.

Create Multiple files upload React components and render to the UI

In this example, we use CDN and babel transpiler to build a React component with a JSX.

This React example includes the CDN path of React, ReactDOM, Axios and Babel libraries.

Note: The CDN based transpilation is not recommended for production. Rather, depend on any build tool like Webpack.

In the previous article, we use Vite tool which performs babel tranpilation with respect to the babel.config.js during the build for production.

The React component class do the following.

  • It binds the multiple file upload variables with the corresponding setters.
  • It defines a control to reset all the variables to the initial state.
  • It returns JSX for the multi-file upload option and preview UI with progress bar.

The React component created in this example is MultiFileUpload. This is rendered into the HTML root created for deploying this React component via ReactDOM.

Initially, the UI shows the file input to upload multiple files. Once the chosen files are submitted, then the preview and progress bar HTML elements will be shown.

This condition is checked based on the useState of the counter that has the number of files posted on submit.

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="style.css" />
  <link rel="stylesheet" href="form.css" />
  <title>Multiple File Upload</title>
</head>

<body>
  <h1>Multiple File Upload</h1>
  <div id="multiple-file-upload-component"></div>

  <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script src="multiple-file-upload.js"></script>

  <script type="text/babel">
    const { useState } = React;
    const { render } = ReactDOM;

    const MultiFileUpload = () => {
      const [files, setFiles] = useState([]);
      const [errorMessage, setErrorMessage] = useState("");
      const [uploadProgress, setUploadProgress] = useState(0);
      const [imagePreview, setImagePreview] = useState([]);
      const [uploadButtonClicked, setUploadButtonClicked] = useState(false);

      function clearState() {
        setFiles([]);
        setErrorMessage("");
        setUploadProgress(0);
        setImagePreview([]);
        setUploadButtonClicked(false);
      }
      return (
        <div>
          {errorMessage && <p className="validation-message">{errorMessage}</p>}
          <div className="file-upload-button">
            <input
              type="file"
              multiple
              accept="image/jpeg, image/png, image/gif"
              onChange={handleFileOnChange}
            />
            <button onClick={handleMultiFileUpload}>Upload</button>
          </div>
          {uploadButtonClicked && (
            <div>
              <div className="image-container">
                {files.length > 0 && (
                  <div>
                    <h3>Preview :</h3>
                  </div>
                )}
                {files.map((file, index) => (
                  <div key={index} style={{ marginBottom: "10px" }}>
                    <div>
                      <img className="preview" src={imagePreview[index]} alt="Preview" />
                    </div>
                  </div>
                ))}
              </div>
              <div className="progress-bar">
                <div
                  className="progress-fill"
                  style={{ width: `${uploadProgress}%` }}
                ></div>
              </div>
            </div>
          )}
        </div>
      );
    };
    ReactDOM.render(<MultiFileUpload />, document.getElementById("multiple-file-upload-component"));
  </script>
</body>

</html>

Bind UI controls with the JS handlers to access files and trigger the upload

This JavaScript contains functions for validating the uploaded file. After validation, it triggers an AJAX request to perform the server-side upload action if no error exists.

The validation is for making sure of the following conditions.

  • It checks if the file is within the allowed size limit.
  • It checks the file extension is one among the allowed types. Those are the jpg, png and gif type of images that can only be uploaded by this example.

Once no error found and the upload request is posted, the multiple images preview are shown. The setImagePreviews() method change the UI state with the recently uploaded image preview.

multiple-file-upload.js

function checkIsAllowedType(selectedFiles) {
  const allowedTypes = ['.jpeg', '.png', '.gif'];
  return selectedFiles.some(file => {
    const fileExtension = file.name.split('.').pop().toLowerCase();
    return allowedTypes.includes(`.${fileExtension}`);
  });
}
async function handleMultipleFileUploadAjax(files, setUploadProgress, setErrorMessage) {
  try {
    const formData = new FormData();
    files.forEach((file) => formData.append('file', file));

    const response = await axios.post('upload-image.php', formData, {
      onUploadProgress: (progressEvent) => {
        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
      },
    });

    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
}

const validateFile = (e, setFiles, setErrorMessage, setImagePreviews, clearState) => {
  clearState();
  const selectedFiles = Array.from(e.target.files);
  const totalSize = selectedFiles.reduce((acc, file) => acc + file.size, 0);
  if (totalSize > 2 * 1024 * 1024) {
    setErrorMessage('Image size exceeds. It should be less than 2MB.');
  } else {
    const isAllowedFiles = checkIsAllowedType(selectedFiles);
    if (!isAllowedFiles) {
      setErrorMessage('Please select only image files (jpeg, png, gif).');
    } else {
      setFiles(selectedFiles);
      const previews = selectedFiles.map((file) => URL.createObjectURL(file));
      setImagePreviews(previews);
    }
  }
};

Acknowledge users with a progress bar and previews

This code is as part of the MultiFileUpload component definition. These two handlers calls setters setImagePreview and setUploadProgress to show the graphical acknowledgement for the user action.

It updates the UI with the uploaded image thumbnails as preview. It also updates the percentage of upload completed in a sliding progress bar.

const handleFileOnChange = (e) => {
    validateFile(
      e,
      setFiles,
      setErrorMessage,
      setImagePreview,
      clearState
    );
  };

  const handleMultiFileUpload = async () => {
    if (files.length === 0) {
      setErrorMessage("Please select atleast a file to upload.");
      return;
    }
    setErrorMessage("");
    setUploadProgress();
    setUploadButtonClicked(true);
    handleMultipleFileUploadAjax(files);
  };

PHP endpoint to process the multiple files upload

upload-image.php

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        if (isset($_FILES['file'])) {
            $uploadDirectory = __DIR__ . '/uploads/';
            if (!file_exists($uploadDirectory)) {
                mkdir($uploadDirectory, 0777, true);
            }

            $file = $_FILES['file'];

            $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
            $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];

            if (in_array($fileExtension, $allowedExtensions)) {
                $newFilename = uniqid() . '.' . $fileExtension;
                $uploadPath = $uploadDirectory . $newFilename;

                if (move_uploaded_file($file['tmp_name'], $uploadPath)) {
                    echo json_encode(['message' => 'File uploaded successfully']);
                } else {
                    echo json_encode(['error' => 'Failed to move the file']);
                }
            } else {
                echo json_encode(['error' => 'File type not allowed']);
            }
        } else {
            echo json_encode(['error' => 'No file found in the request']);
        }
    } catch (Exception $e) {
        echo json_encode(['error' => $e->getMessage()]);
    }
} else {
    echo json_encode(['error' => 'Invalid request method']);
}

View demo Download

Comments to “Multiple File Upload in React with Progress Bar”

Leave a Reply

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

↑ Back to Top

Share this page