233

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?

3
  • 4
    Triggering browser download from front-end is not a reliable way to do it. You should create endpoint that when called, will provide the correct response headers, thus triggering the browser download. Front-end code can only do so much. The 'download' attribute for example, might just open the file in a new tab depending on the browser. Commented Jun 5, 2018 at 8:08
  • 1
    From my understanding, you are saying that it can be achieved by rest api with correct response headers, is it right? Commented Jun 5, 2018 at 8:15
  • 1
    Yes. I didn't know how to attach a link in comment, so I posted an answer. Commented Jun 5, 2018 at 8:20

27 Answers 27

147

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you @Brian Li for that. Nice post. A question, how are you managing the memory? When using createObjectURL typically you would use revokeObjectURL to remove the object from the memory. I think, but could be wrong, to not do this could cause issues with larger files especially with repeat downloads.
Looking into the code from js-file-download, they are using a short timer. Its what I have now implemented in my code based upon yours. Thank you for your post. setTimeout(function () { document.body.removeChild(link); window.URL.revokeObjectURL(url); }, 200);
Why new Blob([blob])? You already have a Blob in blob
105

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

4 Comments

Anchor tag opens image in same browser tab (preview of image) and then we need to manually save the image. I am trying to achieve automatic download on button click, without opening file preview
Refer to this for more details: stackoverflow.com/questions/2408146/…
download observes the same-origin-policy FYI
Update for 2021: the download property is supported in 95% of browsers: caniuse.com/download so... 🤷‍♂️
85

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.

2 Comments

I try for zip file, the file downloads, and never unzips properly. Someone might know the reason for it? Btw, I find a workaround: stackoverflow.com/a/62855917/7622204
@Javier López what if it's a link from API response but i want to somehow use custom filename instead of server-generated file name using React Router?
83

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.

5 Comments

What you should do is, create an endpoint that when called, will provide the correct response headers, thus triggering the browser download.... What are those header and how does specifics headers can trigger browser download ? thanks
Hey, sorry for the late reply. The headers in question are Content-Type and Content-Disposition.
setting 'Content-Disposition' header won't start the download for an XHR or fetch API call
I have created one end-point with the required headers in express.js. Basically, I have a res.download(). It downloads the file when I open up a new tab and hit the end-point. When I hit that using my application: onClick={() => axios.get("..")}, the download doesn't work. What could be wrong with my approach?
@PiyushAggarwal You need to use FileSaver, see: github.com/eligrey/FileSaver.js/wiki/…
51

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.

4 Comments

this works well, asks for save as, which is what i was looking for
I can't open file after download, File error : "The file (nameFile) could not be opened. It may be damaged or use a file format that Preview doesn’t recognize." Did I miss anything?
@hoanghuychh it may be that it is not finding the file in the path you are providing I've seen this issue frequently occur when providing a relative path to the file instead of an absolute path. (as the current URL where the download action started may not match the base path where the file is located)
It is almost 5 years and this remains the best and working solution for me. Thanks
41

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);

2 Comments

Except that browser will open a PDF, and I want it to download. Not open.
Its also working with react and node/HAPI. I have added below link.href = your_link.pdf; link.download = yourFileName; Thanks a lot.
33

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.

Comments

19

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>

Comments

12

import resume from '../assets/data/resume.pdf';

<a href={resume} download="YourName resume.pdf"> Download CV </a>

1 Comment

I was able to add an epub file in the same way
12
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>)
}

2 Comments

Be aware that if the page is not reloaded in your app, the blob passed to "URL.createObjectURL" will remain in memory until you call URL.revokeObjectUrl. For a long-running app, this may lead to wasted memory and perfomance issues.
Thank you. responseType: 'blob' solved my problem.
6

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.

Steps

3 steps:

  1. Make use of file saver in project: npmjs/package/file-saver (npm install file-saver or something)
  2. Place the file in your project You say it's in the components folder. Well, chances are if you've got web-pack it's going to try and minify it.(someone please pinpoint what webpack would do with an asset file in components folder), and so I don't think it's what you'd want. So, I suggest to place the asset into the 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.
  3. Refer to the file in your project Sample code:
    import FileSaver from 'file-saver';
    FileSaver.saveAs(
        process.env.PUBLIC_URL + "/resource/file.anyType",
        "fileNameYouWishCustomerToDownLoadAs.anyType");
    

Source

Appendix

1 Comment

This is kind of a cool library. It still opened PDFs in a new tab unfortunately though. It makes opening the file on the fly a really clean process though.
6

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>

2 Comments

It will open pdf in new tab while using fileSaver
@DivyeshKanzariya how can you resolve this question?
5

Great and fast solution:

window.open('https://myapi.com/download/file-name')

Comments

5

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>
</>

Comments

3

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;

Comments

2

Download file


For downloading you can use multiple ways as been explained above, moreover I will also provide my strategy for this scenario.

  1. npm install --save react-download-link
  2. import DownloadLink from "react-download-link";
  3. React download link for client side cache data
    <DownloadLink 
        label="Download" 
        filename="fileName.txt"
        exportFile={() => "Client side cache data here…"}
    />
    
  4. Download link for client side cache data with Promises
    <DownloadLink
        label="Download with Promise"
        filename="fileName.txt"
        exportFile={() => Promise.resolve("cached data here …")}
    />
    
  5. Download link for data from URL with Promises Function to Fetch data from URL
     getDataFromURL = (url) => new Promise((resolve, reject) => {
        setTimeout(() => {
            fetch(url)
                .then(response => response.text())
                .then(data => {
                    resolve(data)
                });
        });
    }, 2000);
    
  6. DownloadLink component calling Fetch function
    <DownloadLink
          label=”Download”
          filename=”filename.txt”
          exportFile={() => Promise.resolve(this. getDataFromURL (url))}
    />
    

Happy coding! ;)

1 Comment

this one is good but is there a way to change label to icon?
2

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.

Comments

1

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");
}

Comments

1

Download from server URL ( amazon s3 )

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")

Comments

1
//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>

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
1
  • to check that there is window object
  • to revoke URL after download
  • createObjectURL can have MediaSource as an argument
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>

Comments

1

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

  }
}

Comments

0

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/

Comments

0

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 :)))

1 Comment

This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
0
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();
}

Comments

0

Its better to use packages. I'm using Nextjs and with 'use client' this works: Focus on Aria-label when building apps to help visually impaired people. Thanks.

Install and import file-saver package as below:

import fileSaver from 'file-saver'

Invoke the saveAs to download.

     <button
      onClick={() => {
        fileSaver.saveAs(
          "https://yolo-chill-buddy.com/myfile.pdf",
          "my-file.pdf"
        );
      }}
      aria-label="Do this and it works - chill"
    >
      <span>Download</span>
    </button>

Comments

-1

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)
            });
    });
});

2 Comments

Please edit the answer and place code in code blocks, your }); is currently outside the blocks.
@DIID Are you asking a question or posting a proposed answer? It is unclear.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.