react-csv를 사용해 axios로 받은 데이터를 CSV형식으로 다운로드 받기

Hannah Lee·2022년 4월 29일
0

요청사항

버튼을 누르면 API 요청을 보내고, 응답 데이터로 CSV 파일을 다운로드 할 수 있게 해주세요.
단, 응답 데이터는 한꺼번에 보낼테니 한 번의 버튼 클릭으로 여러 개의 CSV 파일을 다운로드 받을 수 있어야 해요.

문제

CSVLink에 onClick 이벤트를 붙여서 axios로 응답 데이터를 받아오면 되는, 생각할 것도 없는 기능인 줄 알았는데...
응답이 끝나기 전에 CSVLink를 클릭했다고 인식이 되어서 초기 데이터(빈 배열)로 CSV를 다운로드 시켜주더라 ㅎㅎ;

해결

  1. CSVLink에 onClick 이벤트를 붙이지 않고, Button에 onClick을 붙여 API 호출
  2. useEffect를 사용해 응답 데이터가 setState 되는 시점을 감지
  3. useEffect 안에서 useRef를 사용해서 CSVLink를 클릭한 것처럼 동작시킴
  4. 한 번의 클릭으로 여러 개의 파일을 다운로드 하게 하려면, useEffect와 useRef를 여러 개 써주면 됨

예시 코드

import React, { useState, useEffect, useRef, memo } from 'react';
import Button from '@material-ui/core/Button';
import { CSVLink } from 'react-csv';
import moment from 'moment';
import axios from 'axios';

const CsvSample= memo((props: any) => {
  const csvLink1: any = useRef();
  const csvLink2: any = useRef();
  const [data1, setData1] = useState<any>([]);
  const [data2, setData2] = useState<any>([]);

  const clickDownload = async () => {
      // params는 알아서 구성
      let params = {
        param1: "param1"
        param2: "param2"
        param3: "param3"
      };

      await axios
        .post('url', params)
        .then((res: any) => {
          parseData(res);
        })
        .catch((err) => {
          console.log(err);
        });
  };

  // axios에 대한 response는 알아서 구성 및 react-csv의 data 형식에 맞게 파싱
  const parseData = (input: any) => {
    let data1_header = input.data.data1.columns;
    let data1_rows = input.data.data1.data;
    let _data1 = [data1_header , ...data1_rows ];

    let data2_header = input.data.data2.columns;
    let data2_rows = input.data.data2.data;
    let _data2 = [data2_header , ...data2_rows ];

    setData1(_data1);
    setData2(_data2);
  };

  useEffect(() => {
    if (data1 && csvLink1 && csvLink1.current && csvLink1.current.link) {
      setTimeout(() => {
        csvLink1.current.link.click();
        setData1([]);
      });
    }
  }, [data1]);

  useEffect(() => {
    if (data2 && csvLink2 && csvLink2.current && csvLink2.current.link) {
      setTimeout(() => {
        csvLink2.current.link.click();
        setData2([]);
      });
    }
  }, [data2]);

  return (
    <div>
      <Button
        variant="outlined"
        color="primary"
        onClick={() => {clickDownload();}}
        size="large"
      >
        Download
     </Button>
     {data1.length > 0 ? (
       <CSVLink
         data={data1}
         ref={csvLink1}
         filename={`csv-1-${moment().format('YYYYMMDD')}`}
         style={{ textDecorationLine: 'none' }}
       />
       ) : undefined}
     {data2.length > 0 ? (
       <CSVLink
         data={data2}
         ref={csvLink2}
         filename={`csv-2-${moment().format('YYYYMMDD')}`}
         style={{ textDecorationLine: 'none' }}
       />
       ) : undefined}
   </div>
  );
});

export default CsvSample;

[참고한 사이트]

profile
프론트 어쩌고

0개의 댓글