10

I've been fighting with this code for days now and I'm still not getting it right.

The problem: I'm working with a form that has a dropzone. On submit handler, I need to save the images' url in an array, but it's always returning as an empty array.

Declaring images array:

const [images, setImages] = useState([]);

Here I get the images' url and try to save them in the array:

const handleSubmit = () => {

      files.forEach(async(file)=> {
          const bodyFormData = new FormData();
          bodyFormData.append('image', file);
          setLoadingUpload(true);
          try {
            const { data } = await Axios.post('/api/uploads', bodyFormData, {
              headers: {
                'Content-Type': 'multipart/form-data',
                Authorization: `Bearer ${userInfo.token}`,
              },
            });
            setImages([...images,data])
            setLoadingUpload(false);
          } catch (error) {
            setErrorUpload(error.message);
            setLoadingUpload(false);
          }
      })
  }

Here I have the submitHandler function where I call the handleSubmit():

const submitHandler = (e) => {

    e.preventDefault();
    handleSubmit();
   dispatch(
        createCard(
          name,
          images,
        )
      );
}

I know it's because of the order it executes the code but I can't find a solution. Thank you very much in advance!!!!

pop
  • 217
  • 1
  • 3
  • 11

3 Answers3

12

Issue

React state updates are asynchronously processed, but the state updater function itself isn't async so you can't wait for the update to happen. You can only ever access the state value from the current render cycle. This is why images is likely still your initial state, an empty array ([]).

const submitHandler = (e) => {
  e.preventDefault();
  handleSubmit(); // <-- enqueues state update for next render
  dispatch(
    createCard(
      name,
      images, // <-- still state from current render cycle
    )
  );
}

Solution

I think you should rethink how you compute the next state of images, do a single update, and then use an useEffect hook to dispatch the action with the updated state value.

const handleSubmit = async () => {
  setLoadingUpload(true);
  try {
    const imagesData = await Promise.all(files.map(file => {
      const bodyFormData = new FormData();
      bodyFormData.append('image', file);
      return Axios.post('/api/uploads', bodyFormData, {
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: `Bearer ${userInfo.token}`,
        },
      });
    }));
    setImages(images => [...images, ...imagesData]);
  } catch(error) {
    setErrorUpload(error.message);
  } finally {
    setLoadingUpload(false);
  }
}

const submitHandler = (e) => {
  e.preventDefault();
  handleSubmit();
}

React.useEffect(() => {
  images.length && name && dispatch(createCard(name, images));
}, [images, name]);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 2
    Omg it worked!!!! And great explanation I got it now :) Thank you very very much!!!!! – pop May 20 '21 at 05:55
0

To prevent race-conditions, you could try to use the setImages with the current value as follows:

setImages(currentImages => [...currentImages, data])

This way, you will use exactly what is currently included in your state, since the images might not be the correct one in this case.

As another tip, instead of looping over your files, I would suggest you map the files, as in files.map(.... With this, you can map all file entries to a promise and at the end, merge them to one promise which contains all requests. So you can simply watch it a bit better.

Martin Seeler
  • 6,874
  • 3
  • 33
  • 45
  • Thank you for the answer! I tried that, but still not working :( I put a console.log(data) before the setImages(currentImages => [...currentImages, data]) and a console.log(images) after it. The data returns "/uploads/1621487791667.jpg" but images is an empty array – pop May 20 '21 at 05:19
0

Just await your map function with an await Promise.all() function. This will resolve all promises and return the filled array