Search code examples
reactjscsvdownloadstreamrails-api

How to get a downloadable file from a readableStream response in a fetch request


I have a react app that sends a POST request to my rails API. I want my API endpoint to generate a csv file and then to send back that file to the react app. I want then the browser to download of the csv file for the end user.

Here's how the endpoint looks like :

  def generate
      // next line builds the csv in tmp directory
      period_recap_csv = period_recap.build_csv
       // next line is supposed to send back the csv as response
      send_file Rails.root.join(period_recap.filepath), filename: period_recap.filename, type: 'text/csv'
    end

On the front end side here's how my request looks like :

export function generateCsvRequest(startDate, endDate) {
  fetch("http://localhost:3000/billing/finance-recaps/generate", {
    method: "post",
    headers: {
      Authorisation: `Token token=${authToken}`,
      'Accept': 'text/csv',
      'Content-Type': 'application/json',
      'X-Key-Inflection': 'camel',
    },
    //make sure to serialize your JSON body
    body: JSON.stringify({
      start_date: startDate,
      end_date: endDate
    })
  })
  .then( (response) => {
    console.log('resp', response);
    return response;
  }).then((data) => {
      // what should I do Here with the ReadableStream I get back ??
      console.log(data);
    }).catch(err => console.error(err));
}

As the response body I get a readableStream : enter image description here

What should I do now with that ReadableStream object to launch the download of that CSV file on the end user browser ?


Solution

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