Uploading file with progress indicator in SPFx webpart using PnPJS in SharePoint

SharePoint provides StartUpload, ContinueUpload and FinishUpload REST APIs to upload large files in chunk. PnPJS Library simplifies the implementation and we have to call only one method to upload file in chunks.

To upload file in chunks in PnPJS, you call addChunked method by supplying a callback function which can be used to track the progress of the upload.

sp.web.getFolderByServerRelativeUrl("/sites/naveegator/Shared%20Documents/")
    .files
    .addChunked(file.name, file,
        data => {
            // Here we update the progress by fetching data.blockNumber and data.totalBlocks
        }, true)
    }

The third parameter in addChunked method is the callback function which provides ability to view in how many chunks the file are being uploaded (totalBlocks) and what is the current chunk (blockNumber) that is being uploaded. Using this information we can create progress bar to show percentage of file uploaded. Below is the React component in SPFx webpart. It uses PorgressIndicator from Office UI Fabric aka Fluent UI to display the progress bar.

import * as React from 'react';
import { ProgressIndicator } from 'office-ui-fabric-react/lib/ProgressIndicator';
import { PrimaryButton } from 'office-ui-fabric-react';
import { sp } from "@pnp/sp/presets/all";

export default class UploadFile extends React.Component<IUploadFileProps, {}> {
  constructor(props: IUploadFileProps, public state: any) {
    super(props);

    this.state = {
      showProgress: false,
      progressLabel: "File upload progress",
      progressDescription: "",
      progressPercent: 0
    };
  }

  public render(): React.ReactElement<IUploadFileProps> {
    return (
      <>
        <input type="file" id="fileInput" /><br />
        <PrimaryButton text="Upload" onClick={this.uploadFile} /> <br />
        <ProgressIndicator
          label={this.state.progressLabel}
          description={this.state.progressDescription}
          percentComplete={this.state.progressPercent}
          barHeight={5} />
      </>
    );
  }

  private uploadFile = () => {
    let input = document.getElementById("fileInput") as HTMLInputElement;
    let file = input.files[0];
    let chunkSize = 40960; // Default chunksize is 10485760. This number was chosen to demonstrate file upload progress
    this.setState({ showProgress: true });
    sp.web.getFolderByServerRelativeUrl("/sites/naveegator/Shared%20Documents/")
      .files
      .addChunked(file.name, file,
        data => {
          let percent = (data.blockNumber / data.totalBlocks);
          this.setState({
            progressPercent: percent,
            progressDescription: `${Math.round(percent * 100)} %`
          });
        }, true,
        chunkSize)
      .then(r => {
        console.log("File uploaded successfully");
        this.setState({
          progressPercent: 1,
          progressDescription: `File upload complete`
        });
      })
      .catch(e => {
        console.log("Error while uploading file");
        console.log(e);
      });

  }
}

interface IUploadFileProps { }

I have used chunkSize as 40960 so that I could display the progress bar for smaller sized file. For actual implementation you can omit that argument.