React-csv 非同步資料和渲染中的問題
在前端開發中,有時你需要將資料(通常為 JSON 格式)匯出為可下載的 csv 格式,對於那些在 ReactJS 相關 Web 應用程式上工作的人來說,有一個名為 react-csv 的套件可以幫助你避免寫入特定的套件...渲染需要的資料。
Issue
To use the react-csv package for transferring JSON data into CSV, you can use either the CSVLink or CSVDownload component, like this:
import { CSVLink } from "react-csv";
<CSVLink data={data} headers={csvHeaders} filename={"export.csv"}>
Export as CSV
</CSVLink>
But a potential problem is that the {data} used for rendering the CSVLink component must be available during which the component is rendered, and if the data is being pulled by async call from other sources, chances are that the {data} hasn't been ready yet when the CSVLink is being rendered, so it ends up rendering a component with empty data, which further causes the downloaded csv file to be empty.
Solution
- Do not render the CSVLink component during page initial rendering because of async data loading. Instead, set a flag to determine whether the CSVLink should be rendered or not based on data availability, and render a normal button which when clicked triggers the CSVLink component to be rendered, and then simulate clicking to the CSVLink componnet to proceed with CSV downloading, like below:
<Button type="primary" icon="download" onClick={this.downloadHandler}>
<FormattedMessage id="btn.download" />
</Button>
{
this.state.active ?
<CSVLink data={data} headers={csvHeaders} filename={"export.csv"} ref={this.exportBtn}>
</CSVLink> :
null
}
- Handle the click event in the normal button and further proceed with CSV download in CSVLink:
downloadHandler = () => {
if (data) {
this.setState({
active: true
});
if (this.isCsvFileReady()) {
this.exportBtn.current.link.click();
} else {
setTimeout(() => {
if (this.isCsvFileReady()) {
this.exportBtn.current.link.click();
}
}, 3000);
}
}
}
isCsvFileReady = () => {
return this.exportBtn &&
this.exportBtn.current &&
this.exportBtn.current.link &&
this.exportBtn.current.link.click &&
typeof this.exportBtn.current.link.click === 'function';
}
Summary
The original issue of empty csv export is solved successfully with the above solution, but it is not perfect and there is still some issue, for example, when the "active" flag is set to "true" by the setState method to render the CSVLink: because 1) the setState method is also async; 2) the CSVLink rendering also takes time, it may not become ready before the click handling happens, for which a quick solution so far is to set a timeout of 3 seconds for the component to render and CSV data to generate, and it only impacts the very first click and seems to work fine.