【React, Next.js】オブジェクトをCSVで出力する

## はじめに

顧客要望で、csvファイルでデータをダウンロードしたいということがあり、
出力方法を毎回調べるのも面倒なので、ここにメモとして残します。

## 概要

- ボタンをクリックしたら、Userオブジェクトのデータがcsv形式でダウンロードできる
- リンクではなく、ボタンがいい

### 完成CSVファイル

|名前|年齢|住所|
|----|----|----|
|木村太郎|25歳|東京都〇〇|
|田中花子|20歳|千葉県〇〇|

### 完成画面イメージ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/5d1abd1b-907b-4f9c-7df1-b5747c2de43b.png" width="200"/>

### 環境

- Next.js v13.5.4
- tailwindCSS
    - daisyUI

## 実装

### 1. Userクラスを定義

- 出力するデータのオブジェクトを定義します
- privateプロパティにするかどうかはプロジェクト判断
    
    ```tsx
    class User {
      constructor(
        private _name: string,
        private _age: string,
        private _address: string
      ) {}
    }
    ```

### 2. keyを定義

- 出力するデータの一行目の項目名になるものを定義
    |名前|年齢|住所|
    |----|----|----|
- UserクラスのStaticメソッドで定義しました

    ```diff_tsx
    class User {
      constructor(
        private _name: string,
        private _age: string,
        private _address: string
      ) {}
    
    +  static key() {
    +    return ['名前', '年齢', '住所']
    +  }
    }
    ```

### 3. valueを定義

- 出力するレコードのvalueを定義します

    ```diff_tsx
    class User {
      constructor(
        private _name: string,
        private _age: string,
        private _address: string
      ) {}
    
    +  value() {
    +    return [this._name, this._age, this._address]
    +  }
    
      static key() {
        return ['名前', '年齢', '住所']
      }
    }
    ```


:::note warn
keyとvalueの要素数・順番に注意
:::

### 4. csvデータ作成メソッドの作成

- 今回出力したいcsvデータの完成系は下のようになデータです。
    ```
    名前,年齢,住所
    木村太郎,25歳,東京都〇〇
    田中花子,20歳,千葉県〇〇
    ```
- 先ほど作成したkeyとvalueを使い、csvデータを作成するメソッドを作成します。
    
    ```tsx
    const convertToCSV = (users: User[]): string => {
      const key = User.key()
      const values = users.map((user) => user.value())
      const array = [key].concat(values)
      
      return array.map((a) => a.join(',')).join('\n')
    }
    ```

### 5. ボタンとアンカーを配置し、ボタンクリック時にダウンロードする

- まずボタンとアンカーを配置し、アンカーには`useRef`を設定しておきます
    
    ```tsx
    const Page = () => {
      const anchorRef = useRef<HTMLAnchorElement>(null)
    
      return (
        <div className='m-5'>
          <button className='btn'>
            DownLoad
          </button>
          <a ref={anchorRef} className='hidden'></a>
        </div>
      )
    }
    
    export default Page
    ```

- ボタンをクリック時にダウンロードが進むように、ダウンロードメソッドを定義し、onClickに設定
- ダウンロードメソッドの引数には、csvデータと、filenameを設定
    
    ```diff_tsx
    const Page = () => {
      const anchorRef = useRef<HTMLAnchorElement>(null)
    
    + const downloadCSV = (csv: string, filename: string) => {}
    
      return (
        <div className='m-5'>
          <button
    +       onClick={() => downloadCSV(convertToCSV(users), 'user_list.csv')}
            className='btn'
          >
            DownLoad
          </button>
          <a ref={anchorRef} className='hidden'></a>
        </div>
      )
    }
    
    export default Page
    ```

- ダウンロードメソッドの詳細を書いていきます。

    ```tsx
    const downloadCSV = (csv: string, filename: string): void => {
      const link = anchorRef.current
      if (!link) return
    
      const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
      const url = URL.createObjectURL(blob)
      link.setAttribute('href', url)
      link.setAttribute('download', filename)
      link.click()
    }
    ```


これでオブジェクトデータのcsv出力ができるはずです。

## 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/e24b48cf-0d9f-4789-648c-8b2c2b5b4afa.png)