Loading data asynchronously and downloading CSV data with a click is one step behind the latest retrieved data
Asked Answered
O

2

6

using the react-csv package, I am loading data asynchronously and then trying to download the retrieved data from the server with one click. The problem I am currently facing is that the click is a step behind the current state. When the component first renders it downloads an empty CSV file and on the second click it downloads the data from the previous fetch, not the current/latest fetch.That is the same situation that is described here , but the solution mentioned there didn't work in my case : se here is the code am working on :

  const [newData, setNewData] = useState([]);
  const csvLink = React.createRef();

   const csvExport = (
    <div>
      <Button className="pull-right title-button" onClick={getData}>
        CSV Export
      </Button>
      <CSVLink data={newData} headers={headers} target="_blank" ref={csvLink}></CSVLink>
    </div>
  );

const getData = async () => {
    await getRetailLinkOutcomes(auth, startDate, endDate, 0, 0, filters, sort, sortDirection, false).then((response) => {
      setNewData(response.records);
      csvLink.current.link.click();
    });
  };
Otes answered 13/11, 2020 at 8:21 Comment(2)
Were you able to resolve this? Having the same issue, where the data is one step behind the current state and it takes two clicks to download the correct dataSpotweld
Update: I resolved my issue by putting the csvLink.current.link.click(); inside a useEffect hook that checks if the data is not null, and also wrapping it in a setTimeout. Referenced here: github.com/react-csv/react-csv/issues/72Spotweld
V
4

Had the same issue, resolved it by not using <CSVLink asyncOnClick> (which was a nightmare with Typescript and flat out didn't work unless the user clicked the button twice or unless data was prefetched)

To handle fetching asynchronous data, you can use a normal button to initiate the async fetch and a conditionally rendered button that downloads the CSV

const [initiateDownload, setInitiateDownload] = useState(false)
const [csvRows, setCsvRows] = useState([])

const fetchCsvData = async () => {
  const response = await fetchData()
  setCsvRows(csvRows)
}

useEffect(() => {
  if (csvRows.length) {
    setInitiateDownload(true)
  }
}, [csvRows])

useEffect(() => {
  // Enables user to download data more than once
  if (initiateDownload) {
    setInitiateDownload(false)
  }
}, [initiateDownload])

return (
  <button onClick={fetchCsvData}>Export CSV</button>
  {initiateDownload && (
    <CSVDownload
      data={csvRows}
      headers={csvHeaders}
      target="_blank"
    />
  )}
)
Violaviolable answered 10/11, 2022 at 18:55 Comment(0)
H
1

Elaborating more on what N.A mentioned in the comments:

  const [data, setData] = useState([])
  const [headers, setHeaders] = useState([])
  const excelRef = useRef()

The function to fecth data :

    function requestAllHistory () {
        console.log('requesting excel history')
        requestAllHistoryForExcel(function (response, error) {
        // data and headers fetched
        setData(data)
        setHeaders(headers)
    })
}

Inside useEffect

  useEffect(() => {
    if (headers.length > 0) {
      console.log('ExportExcel useEffect', headers)
      excelRef.current.link.click()
    }
  }, [headers])

The CSVLink should not be displayed on the UI (display:'none')

   return (
    <span>
      <button
        type='button'
        className='btn btn-lg btn-dark'
        onClick={() => requestAllHistory()}
      >
        <span> Export to Excel </span>
        <span className='download'></span>
      </button>
      <CSVLink
        data={data}
        headers={headers}
        filename={filename}
        ref={excelRef}
        className='hiddenExcel'
        target='_blank'
      ></CSVLink>
    </span>
  )
Hardboard answered 20/3, 2022 at 7:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.