await useState in React
Asked Answered
B

3

11

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!!!!

Bisque answered 20/5, 2021 at 5:5 Comment(0)
B
13

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]);
Breakout answered 20/5, 2021 at 5:17 Comment(1)
Omg it worked!!!! And great explanation I got it now :) Thank you very very much!!!!!Bisque
V
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.

Velour answered 20/5, 2021 at 5:10 Comment(1)
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 arrayBisque
K
0

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

Kinesics answered 20/11, 2022 at 18:37 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.