I had created a bootstrap table that displays a list of items with a download button for each row. The download button calls an API that uses application/octet-stream to return a zip file. The download button’s handler looks like this:

...

return fetch(`${reqUrl}`, {
  headers: new Headers({
    "Accept": "application/octet-stream"
  })
})
.then((response) => {
  return response.blob()
})
.then(blob => {
  var url = window.URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.href = url;
  a.download = `${fileName}`;
  document.body.appendChild(a);
  a.click();
  a.remove();
});

...

Some of the files are over a 100MBs so there is sometimes a very long delay between clicking the button and seeing the download. So adding a load spinner would enhance the user experience.

One solution I found (Download API Files With React & Fetch) was to use a state variable:

...

this.state = {
  inProgress: false,
};

...

The download hander can then use that variable:

this.setState({
  inProgress: true
}, () => {
  return fetch(`${reqUrl}`, {
    headers: new Headers({
      "Accept": "application/octet-stream"
    })
  })
  .then((response) => {
    return response.blob()
  })
  .then(blob => {
    var url = window.URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = `${fileName}`;
    document.body.appendChild(a);
    a.click();
    a.remove();

    this.setState({ inProgress: false })
  });
});

The table div can use this same variable to change the opacity of the table and to show the loader:

return (
  <div style={{ opacity: this.state.inProgress ? '.5' : '1' }}>
    <BootstrapTable>
      ...
    </BootstrapTable>
    {this.state.inProgress ? (
      <>
      <div className="loading-overlay">
        <i className="loading-icon"></i>
        <div>Loading...</div>
      </div>
      </>
    ) : null}
  </div>
);

That’s it!