ant design - Upload コンポーネントでファイルをローカルファイルとして扱いたい

antDesignはフロントエンドにおけるデザインシステムの一つで、最近業務アプリケーション開発で結構利用させていただいています。

使い勝手も良く重宝しているのですが、ファイルアップロードで利用するUploadにて、「ローカル（ブラウザ）にファイルを保存しておき、submitボタンを押してバックエンドに送信する」ということの実現で少し困りました。

というのも、ファイル添付時、Uploadコンポーネントはデフォルトで、設定したULRにファイルを転送するという機構になっています。

それを設定しない or 適当なurlを設定しておくなどで良さそうかと思ったのですが、リクエスト結果に応じてファイルアップロードが正常に完了した / 完了しなかった というステータスを持つため、ローカルにファイルはあるものの、画面上はアップロードに失敗したような状態となってしまいます。

備忘録として、その一つの対処法を記載します。

参考記事：

https://stackoverflow.com/questions/51514757/action-function-is-required-with-antd-upload-control-but-i-dont-need-it

# 実装
少し量が多かったため、適当なフックを作成しています。
また、テストプロジェクトを作成し、最初のコンポーネント（App.tsx）にuploadを配置した形です。
後ほど少し解説しますが、ひとまず全体像です。


```typescript:use-upload-file.ts
import { UploadFile, UploadProps, message } from 'antd'
import { useEffect, useState } from 'react'

export const useUploadFile = () => {
  const [fileList, setFileList] = useState<UploadFile[]>([])

  useEffect(() => {
    console.log('File list updated:', fileList)
  })

  const handleFileChange: UploadProps['onChange'] = (info) => {
    if (info.file.status !== 'uploading') {
      console.log(info.file, info.fileList)
    }

    if (info.file.status === 'done') {
      setFileList(info.fileList)
      message.success(`${info.file.name} file uploaded successfully`)
    } else if (info.file.status === 'error') {
      message.error(`${info.file.name} file upload failed.`)
    } else {
      setFileList(info.fileList)
    }
  }

  return { handleFileChange, fileList }
}
```

```tsx:App.tsx
import { Button, Upload, UploadProps } from 'antd'
import { useUploadFile } from './-hooks/use-upload-file'
import { UploadOutlined } from '@ant-design/icons';
import './App.css'
import React from 'react'

function App() {
  const { handleFileChange, fileList } = useUploadFile()
  const props: UploadProps = {
    name: 'test',
    onChange: handleFileChange,
    maxCount: 1,
    // beforeUploadにfalseを返す関数を渡す
    beforeUpload: (() => false)
  }
  const handleSubmit = async () => {
    try {
      // ファイル送信メソッドなど
      console.log(fileList[0])
    } catch (e) {
      console.log(e)
    }
  }

  return (
    <div>
      <Upload {...props} fileList={fileList}>
        <Button icon={<UploadOutlined />}>Click to Upload</Button>
      </Upload>
      <Button onClick={handleSubmit}>submit</Button>
    </div>
  )
}

export default App
```

結果：
ファイルアップロードは正常に完了した表示になり、onSubmitで値が取れていることが確認できます。
(orginFileObjにファイルオブジェクトが入っています。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704095/8eb82bab-613e-4752-b04f-6ef138bf047f.png)

# ポイント
beforeUploadに、falseを返す関数を渡したことです。
公式の説明をみると、beforeUploadの説明は以下のようになっています。
> Hook function which will be executed before uploading. Uploading will be stopped with false or a rejected Promise returned. When returned value is Upload.LIST_IGNORE, the list of files that have been uploaded will ignore it. Warning：this function is not supported in IE9

この、falseの場合アップロード処理が止まるというところを利用している感じですね。
> Uploading will be stopped with false

ただ、FileListにはファイルが保存されているので、submitで送信などで扱うことができます。
useStateなどでファイルリストを管理すると、その後の操作がよりやりやすいのかなと思います。

# 余談
ここからは本当に余談ですが、最初は以下のような実装をしていました。
```typescript
// ダミーリクエスト関数を作成
// (antd - uploadの)onSuccessを受け取り、okを返す
export const dummyRequest = (options: { onSuccess?: (response: unknown) => void }) => {
  const { onSuccess } = options
  setTimeout(() => {
    if (onSuccess) {
      onSuccess('ok')
    }
  }, 0)
}

// Uploadに渡すPropsのcustomRequestに上記関数を設定
  const props: UploadProps = {
    name: 'test',
    onChange: handleFileChange,
    maxCount: 1,
    customRequest: dummyRequest
  }
```

参考にしていたstackOverflowの記事では、最初にこちらの解決方法が記載されており、それをtypeScript verにしたものです。

こちらは、upload url等を渡さないため、外部へのアップロードは起こらない。その後、ファイルアップロード成功時のコールバック関数を受け取りダミーのステータスを返すという実装になります。

こちらでも同じような動作を得ることができますが、「単に外部でのアップロードを無しにしたい」というモチベーションの場合、beforeUploadでfalseを返してあげ得る方がシンプルに実装できますね。

※ちゃんと最後までstackOverflowを見ていれば回り道せず済んだかもしれないですが、日本語だとあまり記事が出てこず、英語で記事がヒットしたことで気がはやりました。
結果、より多くの時間をかけてしまったなと反省です。