antDesignはフロントエンドにおけるデザインシステムの一つで、最近業務アプリケーション開発で結構利用させていただいています。
使い勝手も良く重宝しているのですが、ファイルアップロードで利用するUploadにて、「ローカル(ブラウザ)にファイルを保存しておき、submitボタンを押してバックエンドに送信する」ということの実現で少し困りました。
というのも、ファイル添付時、Uploadコンポーネントはデフォルトで、設定したULRにファイルを転送するという機構になっています。
それを設定しない or 適当なurlを設定しておくなどで良さそうかと思ったのですが、リクエスト結果に応じてファイルアップロードが正常に完了した / 完了しなかった というステータスを持つため、ローカルにファイルはあるものの、画面上はアップロードに失敗したような状態となってしまいます。
備忘録として、その一つの対処法を記載します。
参考記事:
実装
少し量が多かったため、適当なフックを作成しています。
また、テストプロジェクトを作成し、最初のコンポーネント(App.tsx)にuploadを配置した形です。
後ほど少し解説しますが、ひとまず全体像です。
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 }
}
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にファイルオブジェクトが入っています。)
ポイント
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などでファイルリストを管理すると、その後の操作がよりやりやすいのかなと思います。
余談
ここからは本当に余談ですが、最初は以下のような実装をしていました。
// ダミーリクエスト関数を作成
// (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を見ていれば回り道せず済んだかもしれないですが、日本語だとあまり記事が出てこず、英語で記事がヒットしたことで気がはやりました。
結果、より多くの時間をかけてしまったなと反省です。