Material UI File Upload example with Axios & Progress Bar

In this tutorial, I will show you way to build Material UI File Upload example with Rest API. The React App uses Axios and Multipart File for making HTTP requests, Material UI for progress bar and other UI components. You also have a display list of files’ information (with download url).

More Practice:
Material UI Image Upload example with Preview
React Material UI examples with a CRUD Application
React JWT Authentication (without Redux) example
React + Redux: JWT Authentication example


Overview

We’re gonna create a Material UI File upload application in that user can:

  • see the upload process (percentage) with progress bar
  • view all uploaded files
  • link to download the file when clicking on the file name

material-ui-file-upload-example-progress-bar

Technology

  • React 17
  • Axios 0.21.0
  • Material UI 4.11.1

Rest API for File Upload & Storage

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

MethodsUrlsActions
POST/uploadupload a File
GET/filesget 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
Spring Boot Multipart File upload (to static folder) example

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

React App for Material UI upload Files

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

material-ui-file-upload-example-project-structure

Let me explain it briefly.

upload-files.service provides methods to save File and get Files using Axios.
upload-files.component contains Material UI upload form, progress bar, display of list files with download url.
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 Material UI File Upload Project

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

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


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

Import Material UI to React App

Run command: npm install @material-ui/core
Or: yarn add @material-ui/core

Initialize Axios for React HTTP Client

Let’s install axios with command: 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"
  }
});

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/upload-files.service.js

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

class UploadFilesService {
  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 UploadFilesService();

– 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 Material UI Component for Upload Files

Let’s create a File Upload UI with Material UI Progress Bar, Box, Button, Typography and ListItem.

First we create a React component template and import UploadFilesService:

components/upload-files.component.js

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

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

  }

  render() {

  }
}

Then we define the state inside constructor() method:

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

    this.state = {
      selectedFiles: undefined,
      currentFile: undefined,
      progress: 0,
      message: "",
      isError: false,
      fileInfos: [],
    };
  }
}

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

export default class UploadFiles extends Component {
  ...
  selectFile(event) {
    this.setState({
      selectedFiles: event.target.files,
    });
  }

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

export default class UploadFiles extends Component {
  ...
  upload() {
    let currentFile = this.state.selectedFiles[0];

    this.setState({
      progress: 0,
      currentFile: currentFile,
    });

    UploadService.upload(currentFile, (event) => {
      this.setState({
        progress: Math.round((100 * event.loaded) / event.total),
      });
    })
      .then((response) => {
        this.setState({
          message: response.data.message,
          isError: false
        });
        return UploadService.getFiles();
      })
      .then((files) => {
        this.setState({
          fileInfos: files.data,
        });
      })
      .catch(() => {
        this.setState({
          progress: 0,
          message: "Could not upload the file!",
          currentFile: undefined,
          isError: true
        });
      });

    this.setState({
      selectedFiles: undefined,
    });
  }

}

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.

isError state will control the message style (color) displays on the UI.

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

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

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

...
import LinearProgress from '@material-ui/core/LinearProgress';
import { Box, Typography, Button, ListItem, withStyles } from '@material-ui/core';

const BorderLinearProgress = withStyles((theme) => ({
  root: {
    height: 15,
    borderRadius: 5,
  },
  colorPrimary: {
    backgroundColor: "#EEEEEE",
  },
  bar: {
    borderRadius: 5,
    backgroundColor: '#1a90ff',
  },
}))(LinearProgress);

export default class UploadFiles extends Component {
  ...
  render() {
    const {
      selectedFiles,
      currentFile,
      progress,
      message,
      fileInfos,
      isError
    } = this.state;
    
    return (
      <div className="mg20">
        {currentFile && (
          <Box className="mb25" display="flex" alignItems="center">
            <Box width="100%" mr={1}>
              <BorderLinearProgress variant="determinate" value={progress} />
            </Box>
            <Box minWidth={35}>
              <Typography variant="body2" color="textSecondary">{`${progress}%`}</Typography>
            </Box>
          </Box>)
        }

        <label htmlFor="btn-upload">
          <input
            id="btn-upload"
            name="btn-upload"
            style={{ display: 'none' }}
            type="file"
            onChange={this.selectFile} />
          <Button
            className="btn-choose"
            variant="outlined"
            component="span" >
             Choose Files
          </Button>
        </label>
        <div className="file-name">
        {selectedFiles && selectedFiles.length > 0 ? selectedFiles[0].name : null}
        </div>
        <Button
          className="btn-upload"
          color="primary"
          variant="contained"
          component="span"
          disabled={!selectedFiles}
          onClick={this.upload}>
          Upload
        </Button>

        <Typography variant="subtitle2" className={`upload-message ${isError ? "error" : ""}`}>
          {message}
        </Typography>

        <Typography variant="h6" className="list-header">
          List of Files
          </Typography>
        <ul className="list-group">
          {fileInfos &&
            fileInfos.map((file, index) => (
              <ListItem
                divider
                key={index}>
                <a href={file.url}>{file.name}</a>
              </ListItem>
            ))}
        </ul>
      </div >
    );
  }
}

In the code above, we use Material UI Progress Bar – Linear:

<LinearProgress variant="determinate" value={progress} />
  • variant could be "determinate" or "buffer" to indicate progress bar style without/with buffer value
  • value to specify the progress by percentage
  • Typography on the right to display progress value
const BorderLinearProgress = withStyles((theme) => ({
  root: { ... },
  colorPrimary: { ... },
  bar: { ... },
}))(LinearProgress);

We also need to call withStyles() with styles (root, colorPrimary, bar) as input parameter to return BorderLinearProgress (higher-order component).

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. The data will be displayed with Material UI ListItem.

Add File Upload Component to App Component

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

import React from "react";
import "./App.css";
import { Typography } from "@material-ui/core";

import UploadFiles from "./components/upload-files.component";

function App() {
  return (
    <div className="container">
      <div className="mg20">
        <Typography variant="h5">bezkoder.com</Typography>
        <Typography variant="h6">Material UI File Upload</Typography>
      </div>

      <UploadFiles />
    </div>
  );
}

export default App;

Configure Port for Material UI File 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 in this post or this post for making Server, 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

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

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

Run this React – Material UI File Upload application: npm start.
Open Browser with url http://localhost:8081/ and check the result.

Further Reading

Conclusion

Today we’re learned how to build an example for File upload using Material UI with React and Axios. We also provide the ability to show list of files, upload progress bar, and to download file from the server.

Happy Learning! See you again.

More Practice:

Source Code

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

For Image upload with preview like this:

material-ui-image-upload-preview-react-example

Please visit: Material UI Image Upload example with Preview, Axios & Progress Bar

Leave a Reply

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