Reputation: 2469
I receive file url as response from api. when user clicks on download button, the file should be downloaded without opening file preview in a new tab. How to achieve this in react js?
Upvotes: 233
Views: 800322
Reputation: 3110
tldr; fetch the file from the url, store it as a local Blob, inject a link element into the DOM, and click it to download the Blob
I had a PDF file that was stored in S3 behind a Cloudfront URL. I wanted the user to be able to click a button and immediately initiate a download without popping open a new tab with a PDF preview. Generally, if a file is hosted at a URL that has a different domain that the site the user is currently on, immediate downloads are blocked by many browsers for user security reasons. If you use this solution, do not initiate the file download unless a user clicks on a button to intentionally download.
In order to get by this, I needed to fetch the file from the URL getting around any CORS policies to save a local Blob that would then be the source of the downloaded file. In the code below, make sure you swap in your own fileURL
, Content-Type
, and FileName
.
fetch('https://cors-anywhere.herokuapp.com/' + fileURL, {
method: 'GET',
headers: {
'Content-Type': 'application/pdf',
},
})
.then((response) => response.blob())
.then((blob) => {
// Create blob link to download
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute(
'download',
`FileName.pdf`,
);
// Append to html link element page
document.body.appendChild(link);
// Start download
link.click();
// Clean up and remove the link
link.parentNode.removeChild(link);
});
This solution references solutions to getting a blob from a URL and using a CORS proxy.
Update As of January 31st, 2021, the cors-anywhere demo hosted on Heroku servers will only allow limited use for testing purposes and cannot be used for production applications. You will have to host your own cors-anywhere server by following cors-anywhere or cors-server.
Upvotes: 144
Reputation: 93
Backend Nodejs with typescript
export const getFile = async (req: Request, res: Response) => {
try {
const requestBody: requestBody = requestBodySchema.parse(req.body);
const document= await YOURMODEL.findOne({ _id: requestBody.documentID });
if (!document) {
res.status(404).send({ message: "document not found" });
return;
}
//MY document has a field called fileContents which has the actual file
//stored as sting and a fileName field
const stringData = document?.fileContents;
const filename = document?.fileName || "nameNotDefined";
if (!stringData) {
res.status(404).send({ message: "File content not found" });
return;
}
//Creating a buffer to send a long string
const buffer = Buffer.from(stringData, "utf-8");
//Setting appropriate headers and sending buffer to frontend
res.setHeader("Content-Type", "text/plain");
res.setHeader("Content-Disposition", `attachment;
filename="${filename}"`);
res.status(200).send(buffer);
return;
} catch (error: any) {
if (error instanceof ZodError) {
res.status(400).json({ message: "Invalid input" });
return;
}
res
.status(500)
.json({ Error: ` ${error}` });
return;
}
};
Frontend : Nextjs|React.js
<button onClick= {()=>handleDownloadFile(_id,fileName)}
<DownloadIcon width={16} height={16} />
</button>
export async function handleDownloadFile(_id,filename) {
try {
const response = await fetch(
`YOURAPIROUTE`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ documentID : _id}),
}
);
if (!response.ok) {
//Handle bad response
}
//Conver buffer response into blob to create a downloadable url
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = URL;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
} catch (error) {
// Handle error, e.g., show an error message or fallback data
}
}
Upvotes: 1
Reputation: 404
export const downloadFileWithLink = (href) => {
let link = document.createElement("a");
let name = (href?.split("/") || [])
name = name[name?.length - 1]
link.setAttribute('download', name);
link.href = href;
document.body.appendChild(link);
link.click();
link.remove();
}
Upvotes: 0
Reputation: 482
export const download = (blob: Blob | MediaSource, filename: string) => {
if (!window) {
return;
}
const blobUrl = window.URL.createObjectURL(blob);
const anchor = window.document.createElement('a');
anchor.download = filename;
anchor.href = blobUrl;
anchor.click();
window.URL.revokeObjectURL(blobUrl);
};
<button onClick={() => download(blob, filename)}>Download</button>
Upvotes: 1
Reputation: 448
I don't recommend using documents in React since accessing DOM directly is not the best practice. Without any library, it's achievable in React way: (Functional component): Create a link and hide it with CSS. Also, the Href attribute should refer to a state where you have your data. Also, with the useRef hook, you can access your link tag. Now imagine you have a function in which you fetch your data from an API, and you can put the data in your state when it's there, and then immediately you can click on your hidden programmatically in you useEffect link via the ref like the following:
const dataLink = useRef();
const [data, setData] = useState();
useEffect(() => {
if (data) {
dataLink.current.click();
}
}, [data]);
const fetchData = async () => {
const response = await fetch("http://example.com/data.json");
const data = await response.json();
setData(data);
};
return (
<a className="hidden-element" download href={data} ref={dataLink}></a>
);
As soon as you call fetchData function in your component, it will automatically download the data which is fetched from the API.
Upvotes: 2
Reputation: 51
If you're looking for just a download button to serve a local file on react
Use this method
import img from "../assets/img/img.png";
export const Downloadbtn = () => (
<>
<a download href={img} > Download Image </a>
</>
Upvotes: 5
Reputation: 11
//Just change react <Link> tag to HTML <a> tag and pass the name of the file to the href attribute
<a
href={details.cv}
className="text-deep my-0"
download={details.cv}
target="_blank"
>
{details.first_name} CV
</a>
Upvotes: 1
Reputation: 4034
Create a Utility Function
export function download(url: string) {
const a = document.createElement("a");
a.href = url;
const clickEvnt = new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
});
a.dispatchEvent(clickEvnt);
a.remove();
}
Usage:
download("link-to-file-in-server")
Upvotes: 1
Reputation: 49
Alot of good answers, I think Brian Li's answer was good. To nitpick, the Content-Type in fetch is for when you're sending data to the server and not receiving.
Also it's important for long lasting apps that hasn't been reloaded yet make sure to do URL.revokeObjectURL(url) after using the link. Should also removeChild the link you've created to if you are no longer using it :)))
Upvotes: 0
Reputation: 17
I have tried this DownloadLink, it is fetching the data from API which I can see using dev tools but it is not trigerring file download. any idea if I am missing anything ?
This is what I have used:
getDataFromURL = (url) => new Promise((resolve, reject) => {
setTimeout(() => {
fetch(url)
.then(response => response.text())
.then(data => {
resolve(data)
});
});
});
Upvotes: -1
Reputation: 5012
Triggering browser download from the frontend is not reliable.
What you should do is, create an endpoint on a server that when called, responds with the correct response headers, thus triggering the browser download.
Frontend code can only do so much. The 'download' attribute for example, might just open the file in a new tab depending on the browser and the type of the file.
The response headers you need to look at are Content-Type
and Content-Disposition
. You should check this answer for a more detailed explanation on those headers.
Upvotes: 80
Reputation: 1157
Great and fast solution:
window.open('https://myapi.com/download/file-name')
Upvotes: 5
Reputation: 593
fetchFile(){
axios({
url: `/someurl/thefiles/${this.props.file.id}`,
method: "GET",
headers: headers,
responseType: "blob" // important
}).then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute(
"download",
`${this.props.file.name}.${this.props.file.mime}`
);
document.body.appendChild(link);
link.click();
// Clean up and remove the link
link.parentNode.removeChild(link);
});
}
render(){
return( <button onClick={this.fetchFile}> Download file </button>)
}
Upvotes: 12
Reputation: 695
import resume from '../assets/data/resume.pdf';
<a href={resume} download="YourName resume.pdf"> Download CV </a>
Upvotes: 12
Reputation: 8626
The package that solved my download link issue was:
npm install --save react-download-link
Use it like this:
fileDownload(axiosResponse.data, 'filename.csv');
You can create e.g. a C# Web API Endpoint like this on the backend side:
[HttpGet("GenerateSitemap")]
public async Task<IActionResult> GenerateSitemap()
{
var sitemapString = "[place-xml-sitemap-string-here]";
var serializedObj = JsonConvert.SerializeObject(obj);
var bytesObj = Encoding.UTF8.GetBytes(serializedObj);
return File(sitemapString.SerializeToByteArray(), "application/octet-stream");
}
Upvotes: 1
Reputation: 593
For downloading you can use multiple ways as been explained above, moreover I will also provide my strategy for this scenario.
npm install --save react-download-link
import DownloadLink from "react-download-link";
<DownloadLink
label="Download"
filename="fileName.txt"
exportFile={() => "Client side cache data here…"}
/>
<DownloadLink
label="Download with Promise"
filename="fileName.txt"
exportFile={() => Promise.resolve("cached data here …")}
/>
getDataFromURL = (url) => new Promise((resolve, reject) => {
setTimeout(() => {
fetch(url)
.then(response => response.text())
.then(data => {
resolve(data)
});
});
}, 2000);
<DownloadLink
label=”Download”
filename=”filename.txt”
exportFile={() => Promise.resolve(this. getDataFromURL (url))}
/>
Upvotes: 2
Reputation: 1
We can user react-download-link component to download content as File.
<DownloadLink
label="Download"
filename="fileName.txt"
exportFile={() => "Client side cache data here…"}/>
https://frugalisminds.com/how-to-download-file-in-react-js-react-download-link/
Upvotes: 0
Reputation: 511
Solution (Work Perfect for React JS, Next JS)
You can use js-file-download and this is my example:
import axios from 'axios'
import fileDownload from 'js-file-download'
...
handleDownload = (url, filename) => {
axios.get(url, {
responseType: 'blob',
})
.then((res) => {
fileDownload(res.data, filename)
})
}
...
<button onClick={() => {this.handleDownload('https://your-website.com/your-image.jpg', 'test-download.jpg')
}}>Download Image</button>
This plugin can download excel and other file types.
Upvotes: 51
Reputation: 1219
You can use FileSaver.js to achieve this goal:
const saveFile = () => {
fileSaver.saveAs(
process.env.REACT_APP_CLIENT_URL + "/resources/cv.pdf",
"MyCV.pdf"
);
};
<button className="cv" onClick={saveFile}>
Download File
</button>
Upvotes: 6
Reputation: 431
This is how I did it in React:
import MyPDF from '../path/to/file.pdf';
<a href={myPDF} download="My_File.pdf"> Download Here </a>
It's important to override the default file name with download="name_of_file_you_want.pdf"
or else the file will get a hash number attached to it when you download.
Upvotes: 33
Reputation: 862
I have the exact same problem, and here is the solution I make use of now: (Note, this seems ideal to me because it keeps the files closely tied to the SinglePageApplication React app, that loads from Amazon S3. So, it's like storing on S3, and in an application, that knows where it is in S3, relatively speaking.
3 steps:
npm install file-saver
or something)public
folder, under a resource
or an asset
name. Webpack doesn't touch the public
folder and index.html
and your resources get copied over in production build as is, where you may refer them as shown in next step.import FileSaver from 'file-saver';
FileSaver.saveAs(
process.env.PUBLIC_URL + "/resource/file.anyType",
"fileNameYouWishCustomerToDownLoadAs.anyType");
Link
component of react-router
react-router-docs/Link. The zip file would download, and somehow would unzip properly. Generally, links have blue color, to inherit parent color scheme, simply add a prop like: style={color: inherit}
or simply assign a class of your CSS library like button button-primary
or something if you're Bootstrappin'Upvotes: 6
Reputation: 341
React gives a security issue when using a
tag with target="_blank"
.
I managed to get it working like that:
<a href={uploadedFileLink} target="_blank" rel="noopener noreferrer" download>
<Button>
<i className="fas fa-download"/>
Download File
</Button>
</a>
Upvotes: 19
Reputation: 4796
You can define a component and use it wherever.
import React from 'react';
import PropTypes from 'prop-types';
export const DownloadLink = ({ to, children, ...rest }) => {
return (
<a
{...rest}
href={to}
download
>
{children}
</a>
);
};
DownloadLink.propTypes = {
to: PropTypes.string,
children: PropTypes.any,
};
export default DownloadLink;
Upvotes: 3
Reputation: 885
If you are using React Router, use this:
<Link to="/files/myfile.pdf" target="_blank" download>Download</Link>
Where /files/myfile.pdf
is inside your public
folder.
Upvotes: 84
Reputation: 3536
browsers are smart enough to detect the link and downloading it directly when clicking on an anchor tag without using the download attribute.
after getting your file link from the api, just use plain javascript by creating anchor tag and delete it after clicking on it dynamically immediately on the fly.
const link = document.createElement('a');
link.href = `your_link.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
Upvotes: 41
Reputation: 5956
This is not related to React. However, you can use the download
attribute on the anchor <a>
element to tell the browser to download the file.
<a href='/somefile.txt' download>Click to download</a>
This is not supported on all browsers: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
Upvotes: 103