React Multiple Images Upload with Preview example

In this React tutorial, I will show you way to build React.js Multiple Images upload example with Preview using Axios and Multipart File for making HTTP requests, Bootstrap for progress bar and display list of images’ information (with download url).

More Practice:
React.js Multiple Image Upload with Preview (functional component)
– Typescript: React Typescript Multiple Image Upload example (with Preview)
React File Upload/Download example with Spring Boot Rest Api
React.js CRUD example to consume Web API
React JWT Authentication & Authorization (without Redux) example
React Redux: JWT Authentication & Authorization example


React Multiple Images upload Overview

We’re gonna create a React.js Multiple Images Upload with Preview application in that user can:

  • see the preview of images that will be uploaded
  • see the upload process (percentage) of each image with progress bars
  • view all uploaded files
  • download link to file when clicking on the file name

Here are screenshots of our React App:

– Before upload:

react-js-multiple-image-upload-with-preview-example-before-upload

– Upload is done:

react-js-multiple-image-upload-with-preview-example

– List of Images Display with download Urls:

react-js-multiple-image-upload-with-preview-example-list-images

– Show status for each image upload:

react-js-multiple-image-upload-with-preview-example-upload-error

Technology

  • React 16/17/18
  • Axios 0.27.2
  • Bootstrap 4

Web API for Image Upload & Storage

Here are APIs that we will use Axios to make HTTP requests:

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:
Node.js Express File Upload Rest API example
Node.js Express File Upload to MongoDB example
Node.js Express File Upload to Google Cloud Storage example
Spring Boot Multipart File upload (to static folder) example

Project Structure

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

react-js-multiple-image-upload-with-preview-example-project-structure

Let me explain it briefly.

file-upload.service provides methods to save File and get Files using Axios.
images-upload.component contains upload form for multiple images, preview, progress bar, list of uploaded images display.
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 React Multiple Images Upload Project

Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-js-multiple-images-upload

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


public
src
components
images-upload.component.js
services
file-upload.service.js
App.css
App.js
index.js
package.json

Import Bootstrap to React Multiple Images Upload App

Run command: npm install [email protected]
Or: yarn add [email protected]

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

import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";

class App extends Component {
  render() {
    // ...
  }
}

export default App;

Initialize Axios for React HTTP Client

Let’s install axios with command: npm install [email protected].
Or: yarn add [email protected].

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"
  }
});

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:

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

services/file-upload.service.js

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

class FileUploadService {
  upload(file, onUploadProgress) {
    let formData = new FormData();

    formData.append("file", file);

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

  getFiles() {
    return http.get("/files");
  }
}

export default new FileUploadService();

– First we import Axios as http from http-common.js.

– Inside upload() 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.

Create Component for Upload multiple Images

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

First we create a React component template and import UploadFilesService:

components/upload-files.component.js

import React, { Component } from "react";
import UploadService from "../services/file-upload.service";

export default class UploadImages extends Component {
  constructor(props) {

  }

  render() {

  }
}

Then we define the state inside constructor() method:

export default class UploadImages extends Component {
  constructor(props) {
    super(props);
    ...

    this.state = {
      selectedFiles: undefined,
      previewImages: [],
      progressInfos: [],
      message: [],

      imageInfos: [],
    };
  }
}

Next we define selectFiles() method which helps us to get the selected Images from <input type="file" > element later.

export default class UploadImages extends Component {
  ...
  selectFiles(event) {
    let images = [];

    for (let i = 0; i < event.target.files.length; i++) {
      images.push(URL.createObjectURL(event.target.files[i]))
    }

    this.setState({
      progressInfos: [],
      message: [],
      selectedFiles: event.target.files,
      previewImages: images
    });
  }
}

We use URL.createObjectURL() to get the image preview URL and put it into previewImages array. This method produces a DOMString including a URL describing the object provided in the parameter. The URL life is tied to the document in the window on which it was created.

Also use selectedFiles array for accessing current selected Files. Every file has a corresponding progress Info (percentage, file name) and index in progressInfos array.

export default class UploadImages extends Component {
  ...
  uploadImages() {
    const selectedFiles = this.state.selectedFiles;

    let _progressInfos = [];

    for (let i = 0; i < selectedFiles.length; i++) {
      _progressInfos.push({ percentage: 0, fileName: selectedFiles[i].name });
    }

    this.setState(
      {
        progressInfos: _progressInfos,
        message: [],
      },
      () => {
        for (let i = 0; i < selectedFiles.length; i++) {
          this.upload(i, selectedFiles[i]);
        }
      }
    );
  }
}

We call UploadService.upload() method on each file with a callback. So create following upload() method:

export default class UploadImages extends Component {
  ...
  upload(idx, file) {
    let _progressInfos = [...this.state.progressInfos];

    UploadService.upload(file, (event) => {
      _progressInfos[idx].percentage = Math.round((100 * event.loaded) / event.total);
      this.setState({
        progressInfos: _progressInfos,
      });
    })
      .then(() => {
        this.setState((prev) => {
          let nextMessage = [...prev.message, "Uploaded the image successfully: " + file.name];
          return {
            message: nextMessage
          };
        });

        return UploadService.getFiles();
      })
      .then((files) => {
        this.setState({
          imageInfos: files.data,
        });
      })
      .catch(() => {
        _progressInfos[idx].percentage = 0;
        this.setState((prev) => {
          let nextMessage = [...prev.message, "Could not upload the image: " + file.name];
          return {
            progressInfos: _progressInfos,
            message: nextMessage
          };
        });
      });
  }
}

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

We also need to do this work in componentDidMount() method:

export default class UploadImages extends Component {
  ...
  componentDidMount() {
    UploadService.getFiles().then((response) => {
      this.setState({
        imageInfos: response.data,
      });
    });
  }
}

Now we implement the render function of the Upload File UI. Add the following code inside render():

export default class UploadImages extends Component {
  ...
  render() {
    const { selectedFiles, previewImages, progressInfos, message, imageInfos } = this.state;

    return (
      <div>
        <div className="row">
          <div className="col-8">
            <label className="btn btn-default p-0">
              <input type="file" multiple accept="image/*" onChange={this.selectFiles} />
            </label>
          </div>

          <div className="col-4">
            <button
              className="btn btn-success btn-sm"
              disabled={!selectedFiles}
              onClick={this.uploadImages}
            >
              Upload
            </button>
          </div>
        </div>

        {progressInfos &&
          progressInfos.map((progressInfo, index) => (
            <div className="mb-2" key={index}>
              <span>{progressInfo.fileName}</span>
              <div className="progress">
                <div
                  className="progress-bar progress-bar-info"
                  role="progressbar"
                  aria-valuenow={progressInfo.percentage}
                  aria-valuemin="0"
                  aria-valuemax="100"
                  style={{ width: progressInfo.percentage + "%" }}
                >
                  {progressInfo.percentage}%
                </div>
              </div>
            </div>
          ))}

        {previewImages && (
          <div>
            {previewImages.map((img, i) => {
              return <img className="preview" src={img} alt={"image-" + i}  key={i}/>;
            })}
          </div>
        )}

        {message.length > 0 && (
          <div className="alert alert-secondary" role="alert">
            <ul>
              {message.map((item, i) => {
                return <li key={i}>{item}</li>;
              })}
            </ul>
          </div>
        )}

        <div className="card mt-3">
          <div className="card-header">List of Files</div>
          <ul className="list-group list-group-flush">
            {imageInfos &&
              imageInfos.map((img, index) => (
                <li className="list-group-item" key={index}>
                  <p><a href={img.url}>{img.name}</a></p>
                  <img src={img.url} alt={img.name} height="80px" />
                </li>
              ))}
          </ul>
        </div>
      </div>
    );
  }
}

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 images, we iterate over imageInfos array using map() function. On each file item, we use img.url as href attribute and img.name for showing text.

The preview of uploading image is shown by iterating previewImages array.

You can simplify import statement with:
Absolute Import in React

Add Images Upload Component to App Component

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

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

import UploadImages from "./components/images-upload.component";

function App() {
  return (
    <div className="container">
      <h3>bezkoder.com</h3>
      <h4>React Multiple Images Upload with Preview</h4>

      <div className="content">
        <UploadImages />
      </div>
    </div>
  );
}

export default App;

Configure Port for React Multiple Image Upload App

Because most of HTTP Server use CORS configuration that accepts resource sharing restricted to some sites or ports, and if you use the Project for making Rest API server in:
- Node.js Express File Upload Rest API example
- Node.js Express File Upload to MongoDB example
- Node.js Express File Upload to Google Cloud Storage example
- Spring Boot Multipart File upload (to static folder) example

You need to configure port for our App.

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

PORT=8081

So our app will run at port 8081.

Run the App

Run this React Multiple Images Upload Axios App with command: npm start.
Or: yarn start.

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

Further Reading

Conclusion

Today we're learned how to build an React application for multiple Images upload with Preview using Axios, Bootstrap with Progress Bars. We also provide the ability to show list of images, upload progress percentage, and to download file from the server.

You can find how to implement the Rest APIs Server at one of following posts:
- Node.js Express File Upload Rest API example
- Node.js Express File Upload to MongoDB example
- Node.js Express File Upload to Google Cloud Storage example
- Spring Boot Multipart File upload (to static folder) example

The source code for the React Client is uploaded to Github.

More Practice:
- React.js Multiple Image Upload with Preview (functional component)
- Typescript: React Typescript Multiple Image Upload example (with Preview)

4 thoughts to “React Multiple Images Upload with Preview example”

  1. Hello, l thank u for your hard work.
    But I have an issue, whenever I try to upload a photo the error message appear “Could not upload the image”
    and in my console I have these errors
    – POST http://localhost:8080/upload net::ERR_CONNECTION_REFUSED4
    -GET http://localhost:8080/files net::ERR_CONNECTION_REFUSED
    -Uncaught (in promise) Error: Network Error
    at createError (createError.js:16:1)
    at XMLHttpRequest.handleError

Comments are closed to reduce spam. If you have any question, please send me an email.