1

I'm creating a picture converter where my JS code gets the file inputs from the user, sends it to my python back-end where they are converted and saved to a folder. Python then sends a response back to JS (react), which updates the state for each file individually as "converted" and re-renders the necessary components.

I have a for loop that sends individual POST requests for each file. This is fine until I want to create a .zip for the entire directory after all files have been converted. My problem lies there. My zip is always returned empty or with incomplete files.

// function which takes the file inputs from user
uploadBatch = async () => {
  const files = this.getFilesFromInput();
  const batch = Math.floor(Math.random() * 999999 + 100000);
  for (let i = 0; i < files.length; i++) {
    // sets the state which will then be updated
    await this.setState(
      {
        files: [
          ...this.state.files,
          {
            // file state values
          }
        ]
      },
      () => {
        const formData = new FormData();
        // appends stuff to form data to send to python
        axios
          .post('/api/upload', formData, {
            headers: {
              'Content-Type': 'multipart/form-data'
            },
            responsetype: 'json'
          })
          .then(response => {
            // update the state for this particular file
          });
      }
    );
  }
  return batch;
};

// function which zips the folder after files are converted
handleUpload = async e => {
  e.preventDefault();
  // shouldn't this next line wait for uploadBatch() to finish before 
  // proceeding?
  const batch = await this.uploadBatch();
  // this successfully zips my files, but it seems to run too soon
  axios.post('/api/zip', { batch: batch }).then(response => {
    console.log(response.data);
  });
};

I have used async/await but I don't think I've used them well. I don't quite fundamentally understand this concept so an explanation would be greatly appreciated.

Darius Mandres
  • 778
  • 1
  • 13
  • 31
  • [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) documentation - note, I doubt `this.setState` returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - which is what `await` awaits – Bravo Nov 20 '18 at 02:15
  • @Bravo no, it just sets the state. I have read multiple documentations but am still not quite how or where exactly I'd include a promise – Darius Mandres Nov 20 '18 at 02:28
  • Possible Duplicate of [What if you can use async/await to make React's setState synchronous](https://stackoverflow.com/questions/53080701/what-if-you-can-use-async-await-to-make-reacts-setstate-synchronous/53080783#53080783) – Shubham Khatri Nov 20 '18 at 05:14

1 Answers1

1

Whenever you call setState(), the component will re-render. You should ideally complete all your actions and call setState() at the end.

Something like this should get things working for you

// function which takes the file inputs from user
uploadBatch = async () => {
  const files = this.getFilesFromInput();
  const batch = Math.floor(Math.random() * 999999 + 100000);
  const files = [];
  for (let i = 0; i < files.length; i++) {
    const formData = new FormData();
    // appends stuff to form data to send to python
    const res = 
      await axios
        .post('/api/upload', formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          },
          responsetype: 'json'
        });

    files.push('push data into files arr');
  }

  return { files, batch };
};

// function which zips the folder after files are converted
handleUpload = async e => {
  e.preventDefault();
  // get batch and files to be uploaded and updated
  const { files, batch } = await this.uploadBatch();
  // this successfully zips my files, but it seems to run too soon
  await axios.post('/api/zip', { batch: batch }).then(response => {
    console.log(response.data);
  });

  // set the state after all actions are done
  this.setState( { files: files });
};
Dinesh Pandiyan
  • 5,814
  • 2
  • 30
  • 49
  • I actually want to re-render each time since I want to display the file status for each file being uploaded (converting... -> converted). As soon as each file is converted, Python sends a response back to which then react updates the state. The problem is that I need `handleUpload` to wait for all the states to be converted before sending the zip request. – Darius Mandres Nov 20 '18 at 02:26
  • Then you need to store everything in state, say `batch`, `allFilesUploaded` and update the status of each file after the upload. You will need to use `allFilesUploaded` state value to trigger the `/api/zip` POST call. – Dinesh Pandiyan Nov 20 '18 at 02:29
  • I thought about that, and that would work for sure. But wouldn't using async/await be a better approach to this? – Darius Mandres Nov 20 '18 at 02:32
  • `setState()` will trigger a re-render every time it's executed and you'll never get to the end of the method even if you use `async-await`. You cannot continue a logic after updating the state by holding off render. – Dinesh Pandiyan Nov 20 '18 at 02:44
  • 1
    I wasn't holding off any render! I just wanted to wait for the for loop to finish before running another post request. Regardless, I fixed it with `async-await` as I suspected would work. I just had to include `promises` and tell my code to wait for those. I'll answer my own question tomorrow for others who might have this issue! Thanks for the help! – Darius Mandres Nov 20 '18 at 03:27