How to work async with react-csv
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
+1
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);
Muchas gracias bro que buen aporte
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();
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
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

@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.
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); ```
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>
Have the react-csv maintainers made any updates on this issue? The README still states this functionality exists but it doesn't work
Wondering if others are still experiencing issues with this?
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: @.***>
ran into the same issue, the async workflow doesn't really work.
I believe it has not been solved at all :(
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.
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.
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.
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))))