react-csv icon indicating copy to clipboard operation
react-csv copied to clipboard

How to work async with react-csv

Open GuySerfaty opened this issue 6 years ago • 19 comments

Let's say I want to load the CSV data lazy from the server just when the user clicks CSVlink

By the readme file, it seems to be easy,

  asyncOnClick={true}
  onClick={(event, done) => {
    axios.post("/spy/user").then(() => {
      done(); // REQUIRED to invoke the logic of component
    });
  }}

But it just not working, codesandbox - first section

There is an open issue but it describes only part of the issue.

I wrote some 'workaround' component with hooks:

import React, { useState, useEffect, useRef, Fragment } from 'react';
import propsTypes from 'prop-types';
import { CSVLink } from 'react-csv';

const CsvExport = ({ asyncExportMethod, children, disable }) => {
  const [csvData, setCsvData] = useState(false);
  const csvInstance = useRef();
  useEffect(() => {
    if (csvData && csvInstance.current && csvInstance.current.link) {
      setTimeout(() => {
        csvInstance.current.link.click();
        setCsvData(false);
      });
    }
  }, [csvData]);
  return (
    <Fragment>
      <div
        onClick={async () => {
          if (disable) {
            return;
          }
          const newCsvData = await asyncExportMethod();
          setCsvData(newCsvData);
        }}
      >
        {children}
      </div>
      {csvData ?
        <CSVLink
          data={csvData.data}
          headers={csvData.headers}
          filename={csvData.filename}
          ref={csvInstance}
        />
      : undefined}
    </Fragment>

  );
};

export default CsvExport;

CsvExport.defaultProps = {
  children: undefined,
  asyncExportMethod: () => null,
  disable: false,
};

CsvExport.propTypes = {
  children: propsTypes.node,
  asyncExportMethod: propsTypes.func,
  disable: propsTypes.bool,
};

What do you think? In case this is a real issue and there is no other way I missed, I think you need to remove the parts that talk about async work from the readme file until we will have a fix for that

GuySerfaty avatar Nov 20 '19 12:11 GuySerfaty

+1

NickAlvesX avatar Mar 27 '20 20:03 NickAlvesX

I was having same problem here... In my case the axios call on the onClick was returning the data correctly, but then the output on the csv was single character: "Ôªø" 🤷‍♂️

Thx a lot @GuySerfaty your workaround helped me a lot. Here is my implementation but with Typescript, in case someone else finds it useful:

import React, { useState, useEffect, useRef, Fragment } from 'react';
import { CSVLink } from 'react-csv';
import { Button } from '@material-ui/core';
import { withStyles, createStyles, Theme } from '@material-ui/core/styles';
import axios from 'axios';

interface HeaderType {
  label: string;
  key: string;
}
interface CsvExportProps {
  url: string;
  children?: Node;
  filename?: string;
  disable?: boolean;
  headers?: HeaderType[];
  label?: string;
  classes?: any;
  transform?: (data: any[]) => any[];
};

type Props = CsvExportProps;

const getStyles = (theme: Theme) =>
  createStyles({
    linkButton: {
      textDecoration: 'none',
      marginLeft: 8
    },
  });

const CsvExport = ({ 
  url,
  children,
  disable,
  label,
  filename,
  headers,
  transform,
  classes,
}: Props) => {
  const [csvData, setCsvData]: any[] = useState([]);
  const csvInstance = useRef<any | null>(null);
  
  const asyncExportMethod = () => axios(url, {
    method: 'GET',
  }).then(response => {
    let data = response.data;
    if (transform) {
      data = transform(response.data);
    }
    setCsvData(data);
  })
  .catch(error => console.log(error));
  
  useEffect(() => {
    if (csvData && csvInstance && csvInstance.current && csvInstance.current.link) {
      setTimeout(() => {
        csvInstance.current.link.click();
        setCsvData([]);
      });
    }
  }, [csvData]);
  
  return (
    <Fragment>
      <div
        onClick={() => {
          if (disable) return;
          asyncExportMethod();
        }}
      >
        {label
          ? <Button component='span' color='primary' variant='outlined' className={classes.linkButton}>{label}</Button>
          : children
        }
      </div>
      {csvData.length > 0 ?
        <CSVLink
          data={csvData}
          headers={headers || Object.keys(csvData[0])}
          filename={filename || 'export.csv'}
          ref={csvInstance}
        />
      : undefined}
    </Fragment>
  );
};

export default withStyles(getStyles)(CsvExport);

sebacampos avatar Apr 18 '20 21:04 sebacampos

Muchas gracias bro que buen aporte

fedewax avatar Apr 22 '20 00:04 fedewax

If csvInstance.current.link.click(); doesn't download the file but redirects to site/blob:2096e5e9-2b06-4520-95e8-09c03e15a404 as i had this issue in SPFX, you should add this: data-interception='off'

<CSVLink
          data={csvData}
          headers={headers || Object.keys(csvData[0])}
          filename={filename || 'export.csv'}
          ref={csvInstance}
          data-interception='off'
        />

Or inside useEffect() hook

csvInstance.current.link.dataset.interception = 'off'
csvInstance.current.link.click();

edisonneza avatar Jun 16 '20 10:06 edisonneza

I have made a fork of this repository in which you can call a function to download the CSV file anywhere you like without using a react component like in this package. This gives more flexibility to implement download CSV file logic and it has solved my similar problem as mentioned here.

Please check it out here: https://github.com/ayush-shta/download-csv

ayush-shta avatar Oct 31 '20 16:10 ayush-shta

I have made a fork of this repository in which you can call a function to download the CSV file anywhere you like without using a react component like in this package. This gives more flexibility to implement download CSV file logic and it has solved my similar problem as mentioned here.

Please check it out here: https://github.com/ayush-shta/download-csv

It doesn't work bro Screenshot 2020-11-05 at 2 12 40 AM

barmola avatar Nov 04 '20 20:11 barmola

@barmola Unfortunately, I cannot help you without more context on how to replicate this. You could always copy the code in core.js file in this library and try to implement the logic yourself or open a new issue in my fork of the repo with more details.

ayush-shta avatar Nov 05 '20 03:11 ayush-shta

Anyone had the download popup keep opening using the first workaround ? @GuySerfaty suggestions what might be wrong ?

const DownloadTable = ({
  tableName, headers, tablesFilter, filterName,
}) => {
  headers = headers.map(header => ({
    label: header.header,
    key: header.key,
  }));
  const csvRef = useRef();
  const [rows, setRows] = useState(false);
  useEffect(() => {
    if (!isEmpty(rows) && csvRef.current && csvRef.current.link) {
      setTimeout(() => {
        csvRef.current.link.click();
        setRows(false);
      });
    }
  }, [rows]);

  const handleDownloadTable = async (e) => {
    try {
      const res = await getTable(JSON.stringify(tablesFilter),
        filterName,
        10000,
        0,
        JSON.stringify([]));
      setRows(res.data.data.results);
    } catch (e) {
      console.error(e);
      return false;
    }
  };

  const renderButton = () => (
    <Button kind="tertiary">
  Export Report
      <div className="download-doc-icon">
        <DocumentDownload20 color="blue" />
      </div>
    </Button>
  );
  return (
    <div
      className="download-button"
      onClick={async () => await handleDownloadTable()}
    >
      {renderButton()}
      {rows
        ? (
          <CSVLink
            data={rows}
            ref={csvRef}
            asyncOnClick
            headers={headers}
            filename={`${tableName || 'Data Table'}.csv`}
          />
        )
        : undefined}
    </div>
  );
};
const mapStateToProps = state => ({
  tablesFilter: state.tablesFilter.tablesFilter,
});
export default connect(mapStateToProps, null)(DownloadTable); ```

OmarSherif96 avatar Jan 18 '21 18:01 OmarSherif96

I am trying to use the component recommended by @GuySerfaty inside a react dropdown. I have the below code but my dropdown doesn't toggle when I click on the item. Does anyone have an idea?

import React, { useState, useEffect, useRef, Fragment } from 'react';
import propsTypes from 'prop-types';
import { CSVLink } from 'react-csv';
import axios from "axios";
import { config } from '../../../config';
import DropdownItem from 'reactstrap/lib/DropdownItem';

const CsvExport = ({view, bucket, group, metric, startDateMs, endDateMs}) => {
  const [csvData, setCsvData] = useState(false);
  const csvInstance = useRef();
  useEffect(() => {
    if (csvData && csvInstance.current && csvInstance.current.link) {
      setTimeout(() => {
        csvInstance.current.link.click();
        setCsvData(false);
      });
    }
  }, [csvData]);

  return (
    <Fragment>
      <button
        onClick={async () => {
            console.log("Fetching data for", view,bucket, group, metric, startDateMs, endDateMs);
          const newCsvData = await axios.get(config.serviceHost + config.downloadPath, {
            params: {
            view: view,
            bucket: bucket,
            group: group,
            metric: metric,
            startDate: startDateMs,
            endDate: endDateMs,
            download: true
            }
        });
        //   console.log(newCsvData.data.payload);
        const filename = [view, bucket, group, metric, Date.now()].join('_') + '.csv';
          setCsvData({
              data: JSON.parse(newCsvData.data.payload),
              filename: filename
          });
        }}
        type="button"
        tabIndex="0"
        role="menuitem"
        className="dropdown-item"
      >
          Download as CSV
      </button>
      {csvData ?
        <CSVLink
          data={csvData.data}
          filename={csvData.filename}
          ref={csvInstance}
        />
      : undefined}
    </Fragment>

  );
};

export default CsvExport;

Main Component

<Card className="flex-fill w-100">
    <CardHeader>
      <div className="card-actions float-right">
        <UncontrolledDropdown>
          <DropdownToggle tag="a">
            <MoreHorizontal />
          </DropdownToggle>
          <DropdownMenu right>
            <CsvExport metric={metric} view={view} bucket={bucket} group={group} startDateMs={this.convertToMs(this.props.startDate)} endDateMs={this.convertToMs(this.props.endDate)}/>
        </DropdownMenu>
        </UncontrolledDropdown>
      </div>
      <CardTitle tag="h5" className="mb-0">
        Languages
      </CardTitle>
    </CardHeader>
 </Card>

harshvardhan93 avatar Apr 13 '21 17:04 harshvardhan93

Have the react-csv maintainers made any updates on this issue? The README still states this functionality exists but it doesn't work

arms1997 avatar Apr 22 '22 20:04 arms1997

Wondering if others are still experiencing issues with this?

brandonbeschta avatar Oct 26 '22 20:10 brandonbeschta

Sin problemas gracias

On Wed, Oct 26, 2022 at 1:27 PM Brandon B @.***> wrote:

Wondering if others are still experiencing issues with this?

— Reply to this email directly, view it on GitHub https://github.com/react-csv/react-csv/issues/189#issuecomment-1292615242, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALDNJKFFJUUZZSJYICEXGNDWFGHZJANCNFSM4JPSCBMA . You are receiving this because you commented.Message ID: @.***>

fedewax avatar Oct 27 '22 16:10 fedewax

ran into the same issue, the async workflow doesn't really work.

missing1984 avatar Nov 08 '22 01:11 missing1984

I believe it has not been solved at all :(

rifqiAly avatar Nov 14 '22 04:11 rifqiAly

Even thought my method is not solving the problem, but this might be the alternative way to export the data to CSV file. We can use useRef to reference on CSVLink like below

<Button className="mb-2" disabled={exportLoading} onClick={onExportStudentHandler}>
	{exportLoading ? 'Exporting data...' : 'Export'}
</Button>
<CSVLink data={exportedStudent ? exportedStudent : []} ref={exportButtonRef} className="hidden"></CSVLink>
useEffect(() => {
	if (exportedStudent !== undefined) {
		setExportLoading(false);
		exportButtonRef.current.link.click();
		setExportedStudent(undefined);
	}
}, [exportedStudent]);

Whenever the custom export button is clicked, it will fetch the data from the backend. useEffect will listen to the exportedStudentchange, then it will trigger the useRef to click on the CSVLink button.

wongfunian avatar Jan 27 '23 16:01 wongfunian

The readme is still incorrect. asyncOnClick does nothing. My CsvFile is empty, the component does not wait for the data being fetched, it just generates the CSV from an empty array.

akshay-nm avatar Apr 11 '23 13:04 akshay-nm

Try this instead : https://github.com/alexcaza/export-to-csv

Way easier to implement. Only takes a couple of lines of code. And it's lightweight.

harvey56 avatar Aug 21 '23 07:08 harvey56

OmarSherif96

You have a CSVLink component in the same div that processes the click. click projected artificially processes both the div and the link. I had the same problem))))

stanislavmartynovbeam avatar Apr 06 '24 00:04 stanislavmartynovbeam