Overview
In many dashboard applications, it's a common requirement to download the API data as a CSV
file. In this blog post we discuss an implementation of a React component that can be used to achieve this. We also present additional benefits of implementing such component in the front-end rather than relying on the APIs alone to provide data as CSV
.
Motivation
Why simply an API Based approach won't work?
-
When we have access to the API endpoints, we can add the download feature using the
Content-Disposition
HTTP header withattachment
attribute. We'll need to define a separate API end-point rather than our usualJson
based API end-point (or at-least some kind of a separateRenderer
for the API request.). While this approach should work in theory, if your front-end is making requests using any standard components likeaxios
, the requests are Ajax requests and the browser does not honor theContent-Disposition
header in such cases. Which, kind of makes sense because in an Ajax request, a user being at the end of the request is not a typical case. This would mean we need to create an explicit user action by creating ananchor
element in the front-end, outside the normal request flow. While this can be made to work, it's a little cumbersome. -
If the API calls you are making are not something you have a control over (ie you only get the data as
Json
), there is no choice but to take care of this in the front-end.
We'll also see a couple of other benefits of doing this in the front-end.
Main Idea
The main challenge we face, with the Ajax requests is that - we have to 'trigger' download request as a user initiated request, rather than it being a simple Ajax request. There is a neat trick that can be used using the Blob
object as shown on the MDN site. So essentially -
- Convert the data returned by API as a String.
- Create a
Blob
object using this string. - Create and Serve Url for the Blob object using
URL.createObjectURL
method. - Make the link downloadable, also by giving a file name if required.
This is shown in the code below.
// https://github.com/hyphenOs/library-frontend/blob/master/src/common/components/CSVDownloader.js
class CSVDownloader extends React.Component {
downloadCSV = () => {
const arr = this.props.data;
const csvString = this.getCSVString(arr);
const csvBlob = this.getCSVBlob(csvString);
this.serveCSVBlob(csvBlob);
};
...
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
serveCSVBlob = csvBlob => {
const url = URL.createObjectURL(csvBlob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "CSVData.csv");
document.body.appendChild(link);
link.click();
};
...
}
Once we decide to process the data on the client side, there are other additional features that can be implemented on the client side. Next we'll look at a couple of such features -
Additional Features
Other than simply translating the data from Json
to CSV
, it's possible to perform some other transformations on the data before the data is converted into CSV
.
-
For instance, often we have an attribute, the API returned values for that attribute can be a
Boolean
value, saytrue/false
or0/1
, but we may want to have a more readable and meaningful value likeOpen/Close
when we save the data asCSV
. This is essentially mapping one value of the attribute to another string. This can be achieved by passing a user defined function that performs such a mapping, we call such a function as amapper
. So what amapper
essentially does is converts a set of attributes from their database (or API) values into more readable values. -
Similarly, it's possible to have custom sorting based on some other attribute of the data rather than the default attribute which is selected by the API. This can also be achieved by a user defined function that sorts the given data using the user defined function. We call such a function
sorter
function.
These are just a couple of examples, other things like filtering out certain column from the CSV
etc. is possible. Generally the code will look something like -
<CSVDownloader
data={data}
mapper={mapper}
sorter={sorter}
/>
...
// Passed functions are applied to transform data
if (mapper && sorter) {
downloadCSV(mapper(sorter(data)));
} else if (mapper) {
downloadCSV(mapper(data));
} else if (sorter) {
downloadCSV(sorter(data));
} else {
downloadCSV(data);
}
Wrapping up
We have seen, rather than taking an API based approach alone, implementing a functionality like downloading CSV
in the front-end, not only allows us to work with our own as well as third party API, but also, allows us to implement additional features. Also, one more benefit of writing such a component is, this component can be reused across several parts of the application, if there are many parts that require this functionality. With some additional effort, it's quite possible to make this component as a completely reusable component completely decoupled from the application logic.
A full source code for an example implementation of this component is available at - - CSVDownloader React Component
Thanks
Mayur Borse: Author of the code mentioned in the blog post and an early draft.