React-csv 异步数据和渲染中的问题

在前端开发中,有时你需要将数据(通常为 JSON 格式)导出为可下载的 csv 格式,对于那些在 ReactJS 相关 Web 应用程序上工作的人来说,有一个名为 react-csv 的包可以帮助你避免破坏 DRY... 但是,在某些特定场景中应用此包时你也可能会遇到问题,比如我即将写的一个场景,其中涉及异步加载但初始 UI 渲染需要的数据。

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.

javascript