Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to work async with react-csv #189

Open
GuySerfaty opened this issue Nov 20, 2019 · 18 comments
Open

How to work async with react-csv #189

GuySerfaty opened this issue Nov 20, 2019 · 18 comments

Comments

@GuySerfaty
Copy link

GuySerfaty commented Nov 20, 2019

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

@NickAlvesX
Copy link

+1

@sebacampos
Copy link

sebacampos commented Apr 18, 2020

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

@fedewax
Copy link

fedewax commented Apr 22, 2020

Muchas gracias bro que buen aporte

@edisonneza
Copy link

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();

@ayush-shta
Copy link

ayush-shta commented Oct 31, 2020

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

@barmola
Copy link

barmola commented Nov 4, 2020

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

@ayush-shta
Copy link

ayush-shta commented Nov 5, 2020

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

@OmarSherif96
Copy link

OmarSherif96 commented Jan 18, 2021

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); ```

@harshvardhan93
Copy link

harshvardhan93 commented Apr 13, 2021

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>

@arms1997
Copy link

arms1997 commented Apr 22, 2022

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

@brandonbeschta
Copy link

Wondering if others are still experiencing issues with this?

@fedewax
Copy link

fedewax commented Oct 27, 2022 via email

@missing1984
Copy link

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

@rifqiAly
Copy link

I believe it has not been solved at all :(

@wongfunian
Copy link

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.

@akshay-nm
Copy link

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.

@harvey56
Copy link

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.

@stanislavmartynovbeam
Copy link

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))))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests