53

Looking for a way to add an "Export to CSV" button to a react-table which is an npmjs package (https://www.npmjs.com/package/react-table).

I need to add a custom button for exporting the table data to an excel sheet in the csv or xls format?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Ajay Singh
  • 732
  • 1
  • 7
  • 16
  • How did you get the data that is present inthe table(like after applying filters). I want to export the data present after applying filters and not the whole original data supplied – raj Jul 10 '18 at 11:35
  • Well i got it working finally via setting a ref in the react table insatance and retreiving the current data through this.reactTable.getResolvedState().sortedData – raj Jul 11 '18 at 03:44
  • you can use a simple function to trigger download https://gist.github.com/xargr/97f160e5ab1bbc513bc7a1acd4ed88e4 – xargr Oct 09 '19 at 17:46

4 Answers4

57

Take a look at this npm library - https://www.npmjs.com/package/react-csv

For example -

import {CSVLink, CSVDownload} from 'react-csv';

const csvData =[
  ['firstname', 'lastname', 'email'] ,
  ['John', 'Doe' , 'john.doe@xyz.com'] ,
  ['Jane', 'Doe' , 'jane.doe@xyz.com']
];
<CSVLink data={csvData} >Download me</CSVLink>
// or
<CSVDownload data={csvData} target="_blank" />
Rishikesh Dhokare
  • 3,559
  • 23
  • 34
  • 7
    ...and use `react-table` documented advice on [`this.reactTable.getResolvedState()`](https://github.com/react-tools/react-table/wiki/FAQ#how-do-i-get-at-the-internal-data-so-i-can-do-things-like-exporting-to-a-file) for getting the `data` prop – DrMeers Aug 29 '18 at 01:02
  • 2
    This package does not work on async calls. – Vivek S Mar 09 '22 at 08:56
55

Here is what the integration will look like:

import React from "react";
import "react-dropdown/style.css";
import "react-table/react-table.css";
import ReactTable from "react-table";
import { CSVLink } from "react-csv";

const columns = [
  {
    Header: "name",
    accessor: "name", // String-based value accessors!
  },
  {
    Header: "age",
    accessor: "age",
  },
];

class AllPostPage extends React.Component {
  constructor(props) {
    super(props);
    this.download = this.download.bind(this);
    this.state = {
      tableproperties: {
        allData: [
          { name: "ramesh", age: "12" },
          { name: "bill", age: "13" },
          { name: "arun", age: "9" },
          { name: "kathy", age: "21" },
        ],
      },
      dataToDownload: [],
    };
  }

  download(event) {
    const currentRecords = this.reactTable.getResolvedState().sortedData;
    var data_to_download = [];
    for (var index = 0; index < currentRecords.length; index++) {
      let record_to_download = {};
      for (var colIndex = 0; colIndex < columns.length; colIndex++) {
        record_to_download[columns[colIndex].Header] =
          currentRecords[index][columns[colIndex].accessor];
      }
      data_to_download.push(record_to_download);
    }
    this.setState({ dataToDownload: data_to_download }, () => {
      // click the CSVLink component to trigger the CSV download
      this.csvLink.link.click();
    });
  }

  render() {
    return (
      <div>
        <div>
          <button onClick={this.download}>Download</button>
        </div>
        <div>
          <CSVLink
            data={this.state.dataToDownload}
            filename="data.csv"
            className="hidden"
            ref={(r) => (this.csvLink = r)}
            target="_blank"
          />
        </div>
        <div>
          <ReactTable
            ref={(r) => (this.reactTable = r)}
            data={this.state.tableproperties.allData}
            columns={columns}
            filterable
            defaultFilterMethod={(filter, row) =>
              String(row[filter.id])
                .toLowerCase()
                .includes(filter.value.toLowerCase())
            }
          />
        </div>
      </div>
    );
  }
}

export default AllPostPage;

This will work with filters as well.

aas395
  • 37
  • 5
best wishes
  • 5,789
  • 1
  • 34
  • 59
  • 3
    Thank you for the example of how to make the link into a button. – timdewolf Dec 20 '18 at 15:49
  • download method can be simplified by seperating headers and data. See this [link](https://www.npmjs.com/package/react-csv#--headers-props). You can map column header to header ids and pass it to CSVLink component as header prop, and sortedData from table ref as data prop. – Himanshu Tanwar Jan 07 '19 at 19:12
  • it should, just try once. – best wishes Apr 30 '19 at 16:32
  • 5
    You don't need `react-csv`, it only makes things more complicated. Use [export-to-csv](https://github.com/alexcaza/export-to-csv) instead. – Finesse Aug 29 '19 at 09:37
  • Thank you for fleshing out the example like you have done. – Andre DiCioccio Nov 29 '19 at 01:29
  • Doesn't seem to work that way anymore:: "error Arrow function should not return assignment no-return-assign" Happened on "ref={(r) => this.csvLink = r}" – Chizl Apr 12 '21 at 23:04
  • 1
    @Switch https://stackoverflow.com/a/48413941/3892213 – best wishes Apr 13 '21 at 03:04
  • @Finesse I wish I found that lib earlier, that one works with async data fetching, at least. And it's way easier to implement. Thanks ! – John Doe Aug 21 '23 at 06:45
11

I have it implemented like this in React + Typescript (no dependency):

  /**
   * @desc get table data as json
   * @param data
   * @param columns
   */
  const getTableDataForExport = (data: any[], columns: any[]) => data?.map((record: any) => columns
.reduce((recordToDownload, column) => (
  { ...recordToDownload, [column.Header]: record[column.accessor] }
), {}));

/**
 * @desc make csv from given data
 * @param rows
 * @param filename
 */
const makeCsv = async (rows: any[], filename: string) => {
  const separator: string = ';';
  const keys: string[] = Object.keys(rows[0]);

const csvContent = `${keys.join(separator)}\n${
  rows.map((row) => keys.map((k) => {
    let cell = row[k] === null || row[k] === undefined ? '' : row[k];

    cell = cell instanceof Date
      ? cell.toLocaleString()
      : cell.toString().replace(/"/g, '""');

    if (cell.search(/("|,|\n)/g) >= 0) {
      cell = `"${cell}"`;
    }
    return cell;
  }).join(separator)).join('\n')}`;

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  if (navigator.msSaveBlob) { // In case of IE 10+
    navigator.msSaveBlob(blob, filename);
  } else {
    const link = document.createElement('a');
    if (link.download !== undefined) {
      // Browsers that support HTML5 download attribute
      const url = URL.createObjectURL(blob);
      link.setAttribute('href', url);
      link.setAttribute('download', filename);
      link.style.visibility = 'hidden';
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }
};

the table:

<Table data={data} columns={columns} />

and the button:

  <button
    type="button"
    onClick={() => makeCsv(getTableDataForExport(data, columns), `${filename}.csv`)}
  >
    Download table data CSV
  </button>
Yoshi
  • 304
  • 4
  • 6
  • 2
    Nice. For anyone wondering what columns should look like, it looks like this: const columns = [ { Header: "first name", accessor: "first_name", }, { Header: "last name", accessor: "last_name", }, ]; The accessor is your value key from your data array. – emmaakachukwu Apr 05 '22 at 10:28
4

I thought I'd piggyback on best wishes' extremely valuable answer with a simplified download implementation.

  export = e => {
    const currentRecords = this.ReactTable.getResolvedState().sortedData;
    this.setState({ dataToDownload: this.dataToDownload(currentRecords, columns) }, () =>
      this.csvLink.link.click()
    );
  }

  dataToDownload = (data, columns) =>
    data.map(record =>
      columns.reduce((recordToDownload, column) => {
        recordToDownload[column.Header] = record[column.accessor];
        return recordToDownload;
      }, {})
    );

I used this to allow multiple table exports in one component by adding additional export functions.

Jeremy J Barth
  • 111
  • 2
  • 7