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?

3
  • 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 Commented Jul 10, 2018 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 Commented Jul 11, 2018 at 3:44
  • you can use a simple function to trigger download gist.github.com/xargr/97f160e5ab1bbc513bc7a1acd4ed88e4 Commented Oct 9, 2019 at 17:46

6 Answers 6

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' , '[email protected]'] ,
  ['Jane', 'Doe' , '[email protected]']
];
<CSVLink data={csvData} >Download me</CSVLink>
// or
<CSVDownload data={csvData} target="_blank" />
Sign up to request clarification or add additional context in comments.

2 Comments

...and use react-table documented advice on this.reactTable.getResolvedState() for getting the data prop
This package does not work on async calls.
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.

8 Comments

Thank you for the example of how to make the link into a button.
download method can be simplified by seperating headers and data. See this link. 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.
it should, just try once.
You don't need react-csv, it only makes things more complicated. Use export-to-csv instead.
|
12

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>

1 Comment

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.
3

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.

Comments

0

First add reference:

const tabRef = useRef(0);

Next 1:

<div className="table_widget">
     <span onClick={goCSV} >CSV</span>
</div>                     
<table className="table"  ref={tabRef}>
    <tr><td>TEXT</td></tr>
</table>

And copy my function 1:

  function encodeb64( str ) {
        return window.btoa(unescape(encodeURIComponent( str )));
    }

Copy function: 2

const goCSV = () =>
    {
 
        var txtBuffer  = "";
        
        const rows = tabRef.current?.rows;
        if(!rows)
        {
            return; 
        }

        Array.from(rows).forEach(row => {

        
            var clsName = row.className;
           
            const cells = Array.from(row.cells);
            cells.forEach(cell => {

                  console.log(cell);

                  var txt = cell.innerHTML;
                  var tdClsName = cell.className;
                  var childElementCount = cell.childElementCount; //

        

                  if(childElementCount == 0) 
                  {
                    txtBuffer = txtBuffer +   txt + ";";
                  }else
                  {
                    txtBuffer = txtBuffer       + ";";
                  }


            });

            txtBuffer = txtBuffer + "\r\n";

          });

          
          txtBuffer = "\ufeff"+txtBuffer; //code utf 

          var encodedString = encodeb64(txtBuffer);
    
          document.location.href = "data:text/csv;base64," + encodedString;
    }

Comments

0

If you are open to switching tables you could try Simple Table

Implementation looks like this

   const tableRef = useRef<TableRefType>(null);

   return (
    <div className="space-y-4">
      <button
        onClick={() => tableRef.current?.exportToCSV()}
      >
        Export to CSV
      </button>
      <SimpleTable
        defaultHeaders={headers}
        editColumns
        height="60vh"
        rowIdAccessor="sku"
        rows={SALES_DATA}
        rowHeight={32}
        selectableCells
        theme={theme}
        tableRef={tableRef}
      />
   </div>

Disclaimer: I built Simple Table

Comments

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.