I have a Dropzone that allows for multiple concurrent uploads and I want to show the progress for all uploads.
In my Dropzone component I have part of the state which is an array of uploads:
const [uploads, setUploads] = useState([])
Each element of the uploads array will be an upload object that has a few values, like so:
const uploading = {
id: 1,
files: <array of files>,
progress: 0
}
Once files/folders are dropped into the dropzone, the "uploading" object will be added to the "uploads state array" and the files will be sent to the backend API, which asynchronously uploads the files to the server.
The backend will periodically send a progress callback to the UI, to update the progress value of the correct element in the uploads state array (see progressCallback below)
What I am currently unable to achieve is to make sure the UI re-renders every time an object in the uploads array is being updated to show progress, so that I can show the progress of all uploads as they happen.
The UI Component looks like this:
export function UploaderDropzone(props) {
const [uploads, setUploads] = useState([])
const progressCallback = useCallback((progressObject, sessionContext, step) => {
const {uploadSessionParameters} = sessionContext
let uploading = {}
// let tmpArray = []
const tmpArray = [...uploads]
if (step === 'progress') {
const filtered = findUploadById(tmpArray, uploadSessionParameters.uploadSessionId)
uploading = filtered[0]
if (uploading) {
const itemIndex = tmpArray.indexOf(uploading)
tmpArray.splice(itemIndex, 1)
uploading.progress = progressObject.percentUpload
tmpArray.push(uploading)
setUploads(tmpArray)
// setUploads(prevUploads => [...prevUploads, uploading])
}
console.log('progress tmpArray = ' + JSON.stringify(tmpArray));
console.log('progress uploads = ' + JSON.stringify(uploads))
}
if (step === 'initialize') {
const uploadNumber = uploads.length + 1
uploading = {
uploadSessionId: uploadSessionParameters.uploadSessionId,
files: sessionContext.files,
uploadNumber: uploadNumber,
uploadName: `Upload #${uploadNumber}`,
sent: false,
progress: 0,
}
tmpArray.push(uploading)
setUploads(tmpArray)
console.log('initialize tmpArray = ' + JSON.stringify(tmpArray))
console.log('initialize uploads = ' + JSON.stringify(uploads))
}
}, [uploads])
const progressBars = uploads.map((upload) => {
return (
<Fragment>
<ProgessBar progress={upload.progress} />
</Fragment>
)
})
// ... more code here ... not required for understanding
return {
<Fragment>
<Dropzone
onDrop={
acceptedFiles => {
const filteredFiles = acceptedFiles.filter((file) =>
validateFile(file))
console.log("Filtered files" + filteredFiles)
if (filteredFiles.length > 0) {
setAlertMsg('')
}
else {
setAlertMsg('No files uploaded.')
}
// call to Node.js backend, passing it the progressCallback
startAsyncUploadSession(filteredFiles, progressCallback);
}
}
/>
{progressBars}
</Fragment>
}
}
The ProgressBar component is very simple:
export function ProgressBar(props) {
const {progress} = props
return (
<Fragment>
<p>`${progress}% uploaded ...`</p>
</Fragment>
)
}
Right now, this code doesn't even show the progress bar even though the uploads state array is constantly being updated in the progressCallback. Since I don't know the number of concurrent uploads that will be done, I cannot set the state in the higher order component and pass it as props, I need the child component (ProgressBar) to receive it as props from the multiple objects in the state array ... but I am clearly missing something ...
Any pointers ? Any hooks I could use to register the progress value of the objects in the uploads state array so that every time the backend updates us on the progress it is reflected in the UI ?
Edit: To include the partial fix suggested by @Robin Zigmond
Edit2: After some debugging, it seems to be a synchronization issue. So I need to add some code and details here.
When files are dropped into the Dropzone, its sends the files to the Node.js backend through a function call to a mock server, the call to startAsyncUploadSession(filteredFiles, progressCallback); in the onDrop event of the Dropzone (which uses the react-dropzone lib).
It would seem that when I call progressCallback later, the state is as it was on the first render, aka uploads state array is an empty array as it was when the files were dropped, not the updated one which contains the object added to uploads array in the 'initializing' step.
So amended question would be "How to make sure that the UI state is up to date when the progressCallback is called later on by the backend ?"