0

I have a file upload component. The behavior is simple: I send one upload request to the back-end per file and as the upload progress increase, I have a bar that should increase with it.

I have a state that holds every selected file and their respective progress, as such:

interface IFiles {
    file: File;
    currentProgress: number;
}
const [selectedFiles, setSelectedFiles] = useState<IFiles[]>([]);

And when the user clicks the upload button, this function will be triggered and call uploadFile for each file in my array state.

const sendFilesHandler = async () => {
        selectedFiles.map(async (file) => {
                const fileType = file.file.type.split('/')[0];
                const formData = new FormData();
                formData.append(fileType, file.file);
                formData.append('filename', file.file.name);
                await uploadFile(formData, updateFileUploadProgress);
        });
    };

Here is what the uploadFile function looks like.

const uploadFile = async (body: FormData, setPercentage: (filename: string, progress: number) => void) => {
    try {
        const options = {
            onUploadProgress: (progressEvent: ProgressEvent) => {
                const { loaded, total } = progressEvent;
                const percent = Math.floor((loaded * 100) / total);
                const fileName = body.get('filename')!.toString();

                if (percent <= 100) {
                    setPercentage(fileName, percent)
                }
            }
        };

        await axios.post(
            "https://nestjs-upload.herokuapp.com/",
            body,
            options
        );

    } catch (error) {
        console.log(error);
    }
};

As you can see, when uploadProgress is triggered it should inform call setPercentage function, which is:

const updateFileUploadProgress = (fileName: string, progress: number) => {
        console.log('Entrada', selectedFiles);
        const currentObjectIndex = selectedFiles.findIndex((x) => fileName === x.file.name);
        const newState = [...selectedFiles];
        newState[currentObjectIndex] = {
            ...newState[currentObjectIndex],
            currentProgress: progress,
        };
        setSelectedFiles(newState);
        console.log('Saída', newState);
    };

And this function should only update the object of my state array where the filenames match. However, it is overriding the whole thing. The behavior is as follows:

enter image description here

So it seems that everything is fine as long as I am updating the same object. But in the moment onUploadProgress is triggered to another object, selectedFiles states becomes its initial state again. What am I missing to make this work properly?

Pelicer
  • 1,348
  • 4
  • 23
  • 54
  • [this](https://stackoverflow.com/questions/33438158/best-way-to-call-an-asynchronous-function-within-map) might help – Sinan Yaman Sep 22 '21 at 14:20
  • Unfortunatly, no. It's a great thread. I tried making implementing Promise.all and making uploadFile function return a promise, but the outcome is the same. – Pelicer Sep 22 '21 at 14:49

1 Answers1

0

I am not sure what is the exact reason behind this behaviour but I came up with the below unexpectedly simple solution after spending 3 hours straight on it.

const updateFileUploadProgress = (fileName: string, progress: number) => {
  setSelectedFiles(prevState => {

    const currentObjectIndex = prevState.findIndex((x) => fileName === x.file.name);
    const newState = [...prevState];

    newState[currentObjectIndex] = {
      ...newState[currentObjectIndex],
      currentProgress: progress,
    };

  return newState;
  })
};

I was able to mimic your problem locally, and solved it with the above approach. I think it was because the function (for some reason) still referenced the initial state even after rerenders.

userharis
  • 412
  • 3
  • 11