How to download fetch response in react as file
Browser technology currently doesn't support downloading a file directly from an Ajax request. The work around is to add a hidden form and submit it behind the scenes to get the browser to trigger the Save dialog.
I'm running a standard Flux implementation so I'm not sure what the exact Redux (Reducer) code should be, but the workflow I just created for a file download goes like this...
- I have a React component called
FileDownload
. All this component does is render a hidden form and then, insidecomponentDidMount
, immediately submit the form and call it'sonDownloadComplete
prop. - I have another React component, we'll call it
Widget
, with a download button/icon (many actually... one for each item in a table).Widget
has corresponding action and store files.Widget
importsFileDownload
. Widget
has two methods related to the download:handleDownload
andhandleDownloadComplete
.Widget
store has a property calleddownloadPath
. It's set tonull
by default. When it's value is set tonull
, there is no file download in progress and theWidget
component does not render theFileDownload
component.- Clicking the button/icon in
Widget
calls thehandleDownload
method which triggers adownloadFile
action. ThedownloadFile
action does NOT make an Ajax request. It dispatches aDOWNLOAD_FILE
event to the store sending along with it thedownloadPath
for the file to download. The store saves thedownloadPath
and emits a change event. - Since there is now a
downloadPath
,Widget
will renderFileDownload
passing in the necessary props includingdownloadPath
as well as thehandleDownloadComplete
method as the value foronDownloadComplete
. - When
FileDownload
is rendered and the form is submitted withmethod="GET"
(POST should work too) andaction={downloadPath}
, the server response will now trigger the browser's Save dialog for the target download file (tested in IE 9/10, latest Firefox and Chrome). - Immediately following the form submit,
onDownloadComplete
/handleDownloadComplete
is called. This triggers another action that dispatches aDOWNLOAD_FILE
event. However, this timedownloadPath
is set tonull
. The store saves thedownloadPath
asnull
and emits a change event. - Since there is no longer a
downloadPath
theFileDownload
component is not rendered inWidget
and the world is a happy place.
Widget.js - partial code only
import FileDownload from './FileDownload';
export default class Widget extends Component {
constructor(props) {
super(props);
this.state = widgetStore.getState().toJS();
}
handleDownload(data) {
widgetActions.downloadFile(data);
}
handleDownloadComplete() {
widgetActions.downloadFile();
}
render() {
const downloadPath = this.state.downloadPath;
return (
// button/icon with click bound to this.handleDownload goes here
{downloadPath &&
<FileDownload
actionPath={downloadPath}
onDownloadComplete={this.handleDownloadComplete}
/>
}
);
}
widgetActions.js - partial code only
export function downloadFile(data) {
let downloadPath = null;
if (data) {
downloadPath = `${apiResource}/${data.fileName}`;
}
appDispatcher.dispatch({
actionType: actionTypes.DOWNLOAD_FILE,
downloadPath
});
}
widgetStore.js - partial code only
let store = Map({
downloadPath: null,
isLoading: false,
// other store properties
});
class WidgetStore extends Store {
constructor() {
super();
this.dispatchToken = appDispatcher.register(action => {
switch (action.actionType) {
case actionTypes.DOWNLOAD_FILE:
store = store.merge({
downloadPath: action.downloadPath,
isLoading: !!action.downloadPath
});
this.emitChange();
break;
FileDownload.js
- complete, fully functional code ready for copy and paste
- React 0.14.7 with Babel 6.x ["es2015", "react", "stage-0"]
- form needs to be display: none
which is what the "hidden" className
is for
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
function getFormInputs() {
const {queryParams} = this.props;
if (queryParams === undefined) {
return null;
}
return Object.keys(queryParams).map((name, index) => {
return (
<input
key={index}
name={name}
type="hidden"
value={queryParams[name]}
/>
);
});
}
export default class FileDownload extends Component {
static propTypes = {
actionPath: PropTypes.string.isRequired,
method: PropTypes.string,
onDownloadComplete: PropTypes.func.isRequired,
queryParams: PropTypes.object
};
static defaultProps = {
method: 'GET'
};
componentDidMount() {
ReactDOM.findDOMNode(this).submit();
this.props.onDownloadComplete();
}
render() {
const {actionPath, method} = this.props;
return (
<form
action={actionPath}
className="hidden"
method={method}
>
{getFormInputs.call(this)}
</form>
);
}
}
How can I download a file using window.fetch?
I temporarily solve this problem by using download.js and blob
.
let download = require('./download.min');
...
function downloadFile(token, fileId) {
let url = `https://www.googleapis.com/drive/v2/files/${fileId}?alt=media`;
return fetch(url, {
method: 'GET',
headers: {
'Authorization': token
}
}).then(function(resp) {
return resp.blob();
}).then(function(blob) {
download(blob);
});
}
It's working for small files, but maybe not working for large files. I think I should dig Stream more.
Set the filename for file download with use of Fetch()
You need to get it from the headers before converting the response into a blob.
const options = {
headers: {
'Content-Disposition': 'attachment; filename="filename.xlsx";'
}
};
fetch("/api", options)
.then( res => {
const filename = res.headers.get('Content-Disposition').split('filename=')[1];
return {response: res.blob(), filename: filename}})
.then( ({response, filename}) => {
var file = window.URL.createObjectURL(response);
window.location.assign(filename);
console.log(filename)
}).catch((e) => {console.log(e)})
How to download single file with multiple chunks in javascript using fetch API
I would loop through the URLs to download the chunks in order and combine them as they download
try{
var urls = ['https://url-1','https://url-2.0','https://url-3.1415']
var fullFile = new Blob();
for (let x = 0; x < urls.length; x++){
await fetch(urls[x]).then((res) => {
if (!res.ok || res.status !== 200) {
throw('Download failed');
}
return res.blob();
}).then(data => fullFile = new Blob([fullFile, data]));
}
let url = window.URL.createObjectURL(fullFile);
let a = document.createElement('a');
a.href = url;
a.download = file.fileName;
a.click();
}
catch(e){console.log(e)};
Download file with react failed even though the request was successful
You should use the blob
method provided by the response object (Doc).
Replace the handleFile
method with:
handleFile(response) {
console.log(response);
response.blob().then((blob) => {
const fileDownloadUrl = URL.createObjectURL(blob);
this.setState({ fileDownloadUrl: response.url }, () => {
this.dofileDownload.click();
URL.revokeObjectURL(fileDownloadUrl);
this.setState({ fileDownloadUrl: "" })
});
});
}
The Blob
constructor accepts an array containing the file content (Doc). Any value which is not the expected type will be converted to string. The default string representation of a response object is [object Response]
.
How to get a downloadable file from a readableStream response in a fetch request
When you use the fetch API, your response object has a bunch of methods to handle the data that you get. The most used is json()
. If you need to download a file coming from the server what you need is blob()
, which works the same way as json()
.
response.blob().then(blob => download(blob))
There are a lot of npm packages to download files. file-saver is one of them. One way that works without dependencies though is by using the a
tag. Something like that:
function download(blob, filename) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
Anyway, using a dependency would cover more edge cases, and it's usually more compatible. I hope that can be useful
If you want to show the pdf in another tab instead of downloading it, you would use window.open
and pass the URL generated by window.URL.createObjectURL
.
function showInOtherTab(blob) {
const url = window.URL.createObjectURL(blob);
window.open(url);
}
Fetch blob downloaded status in a function
I remade the function and now it's working as I expected:
export const download = async (url, filename) => {
let response = await fetch(url, {
mode: 'no-cors'
/*
* ALTERNATIVE MODE {
mode: 'cors'
}
*
*/
});
try {
let data = await response.blob();
let elm = document.createElement('a'); // CREATE A LINK ELEMENT IN DOM
elm.href = URL.createObjectURL(data); // SET LINK ELEMENTS CONTENTS
elm.setAttribute('download', filename); // SET ELEMENT CREATED 'ATTRIBUTE' TO DOWNLOAD, FILENAME PARAM AUTOMATICALLY
elm.click(); // TRIGGER ELEMENT TO DOWNLOAD
elm.remove();
}
catch(err) {
console.log(err);
}
}
To call the function in code I've used anonymous function (normal func I hope also can be used):
(async () => {
await download('/api/hrreportbyhours',"Report "+getDDMMYYY(new Date())+".xlsx");
await setBtnLoad1(false);
})();
How to download a file through an API in React?
As I am not able to add comments so posting as answer.
I have tried the same thing and posted the question for same in this link.
For post method i get the success with fetch as below.
fetch("url",
{
method: "POST",
headers: { "Content-Type": "application/json",'Authorization': 'Bearer ' + window.localStorage["Access_Token"]},
body:data
}).then(response => response.blob()).then(response => ...*your code for download*... )
You are getting corrupted file because you are not receiving content as blob or arraybuffer.
Related Topics
How to Restrict Past Dates in Html5 Input Type Date
How to Set a Value to a File Input in HTML
Check Whether an Input String Contains a Number in JavaScript
How to Wait for One Function to Finish Before Continuing
Change Button Color When Click on the Button
How to Open a Url Link from JavaScript Inside a Google Apps Script HTML Google Site Gadget
How to Array Filter With Multiple Condition in JavaScript
Javascript Return True or Return False When and How to Use It
Convert Image from Url to Base64
Loading Screen Gets Stuck While Processing Http Request
How to Load All the Images from One of My Folder into My Web Page, Using Jquery/Javascript
How to Convert File to Base64 in JavaScript
How to Convert Raw Mp3 Binary into Blob Url and Play It in Audio Tag
How to Bind on Click Event on Dynamically Created Button in Angular 6
How to Hide Div When You Scroll to Bottom and Show It Again When Scroll Up to Top