Мы хотим разместить несколько файлов параллельно и отображать ход загрузки.
Наша первая попытка решить это - использовать setState и предыдущее состояние в обратном вызове uploadProgress:
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
// For clarity: files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
setPendingUploads(files);
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
// Problem: pendingUploads is always empty, we do not get the updated value from last update
setPendingUploads({
...pendingUploads,
[id]: {
...pendingUploads[id],
progress: `${percentCompleted}%`
}
});
};
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<>
<div>
{_.map(pendingUpload, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</>
);
};
pendingUploadsвсегда пусто, поэтому значение прогресса отображается только для одного файла за раз.
Как правильно это сделать? На данный момент у нас есть 2 рабочих примера:
1) Решение 1
- Сохранить карту ожидающей загрузки в состоянии (pendingUploads)
- Инициируйте обновление прогресса (progressUpdate), используя setState внутри handleUploadProgress (обратный вызов axios)
- Обновление pendingUploads в использованииEffect при изменении progressUpdate
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
const [progressUpdate, setProgressUpdate] = useState({ id: "", value: "" });
useEffect(() => {
setPendingUploads({
...pendingUploads,
[progressUpdate.id]: {
...pendingUploads[progressUpdate.id],
progress: progressUpdate.value
}
});
}, [progressUpdate]);
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
setProgressUpdate({ id, value: `${percentCompleted}%` });
};
// files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
setPendingUploads(files);
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<div>
<div>
{_.map(pendingUploads, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</div>
);
};
2) Решение 2
- Измените файлы в обратном вызове uploadProgress и используйте измененное значение для обновления состояния компонента
const FileUpload = () => {
const [pendingUploads, setPendingUploads] = useState({});
// files = Record<string, {id: string, progress: string, data: FormData}>
const handleChange = files => {
const handleUploadProgress = (progressEvent, id) => {
const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
files[id].progress = `${percentCompleted}%`;
setPendingUploads({ ...files });
};
files.forEach(file => {
axios.post("/files", file.data, {
onUploadProgress: e => handleUploadProgress(e, file.id)
});
});
};
return (
<>
<div>
{_.map(pendingUpload, (pendingUpload, idx) => (
<div key={idx}>
{pendingUpload.id}: {pendingUpload.progress}
</div>
))}
</div>
<input multiple type="file" onChange={handleChange} />
</>
);
};
Любой отзыв или рабочий пример, показывающий лучшие практики для данного конкретного случаяДобро пожаловать.
Спасибо