Home > Net >  How Do I wait for a useState hook value to update without using useEffect?
How Do I wait for a useState hook value to update without using useEffect?

Time:11-08

I have a social media webapp with posts. I am trying to allow users to upload an image to Cloudinary from a React front end. There is sometimes an image attached to a post and sometimes not.

If there is an image, I want to wait for the image to be uploaded to Cloudinary and then, once the image URL has been returned by the API, then proceed to upload that imageURL to my Mongo db along with the other post data for permanent storage.

The issue I have is that I need to wait for the imageURL to be returned from the API before posting the data to my db. Currently I have a function uploadImage() that is attached to a handlesubmit() function attached to a form the user fills in to set the image and post content.

My attempt is to have a useState hook setImgURL that is updated when the API returns with the image URL and then use this variable in another fetch() post request to my mongodb db for storage.

However I have since learned that useState hooks are asynchronous. This causes my post image URL to be added to the mongodb with a blank value before the API actually returns with the actual image URL and hence it is lost (especially true if the user submits the form quickly before Cloudinary has given me the image URL).

How can I wait for the image to be uploaded to Cloudinary, get the URL, before posting the final location of it to my db?

I don't think I can use a useEffect() hook which is the standard advice to solve this, since I only get one shot to upload the imageURL to the backend db. I need to wait until I have that value before uploading the post details to mongo. I guess I could get the new post ID and then do a useEffect to do a put update request to the backend with the Cloudinary image URL when it comes through but this seems like the wrong way to do it.

Code:

    const [imgURL, setImgURL] = useState(null);

    const uploadImage = async (image) => {
        console.log('uploading image');
        const formData = new FormData();
        formData.append('file', image);
        formData.append("upload_preset", presetValue);
        try{
            const response = await fetch(APIendPointURL,
                {
                    method: 'POST',
                    body: formData
                });
            const data = await response.json();
            console.log(data);
            setImgURL(data.secure_url);
            console.log(imgURL)
        } catch(err){
            console.log(err);
            setErrors(err);
        }
    }

I am calling uploadImage() in a form handleSubmit() function:

const handleSubmit = async (e) => {
        if (image){
            await UploadImage(image);
        }

        //send post data to mongodb
        try{
            const response = await fetch('http://localhost:3000/api/v1/post/create',
                {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': localStorage.getItem('token')
                    },
                    body: JSON.stringify({
                        content: e.target.content.value,
                        imgURL: imgURL
                    })
                });
            const data = await response.json();
            console.log(data);
        } catch(err){
            console.log(err);
            setErrors(err);
        }
    }

CodePudding user response:

I think you don't need to set the imgURL as a state.

const uploadImage = async (image) => {
    console.log('uploading image');
    const formData = new FormData();
    formData.append('file', image);
    formData.append("upload_preset", presetValue);
    try{
        const response = await fetch(APIendPointURL,
            {
                method: 'POST',
                body: formData
            });
        const data = await response.json();
        console.log(data);
        return data.secure_url; // NOT setImgURL(data.secure_url);
        
    } catch(err){
        console.log(err);
        setErrors(err);
    }
}

const handleSubmit = async (e) => {
    let imgURL = "";
    if (image){
        imgURL = await uploadImage(image);
    }

    //send post data to mongodb
    try{
        const response = await fetch('http://localhost:3000/api/v1/post/create',
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': localStorage.getItem('token')
                },
                body: JSON.stringify({
                    content: e.target.content.value,
                    imgURL: imgURL
                })
            });
        const data = await response.json();
        console.log(data);
    } catch(err){
        console.log(err);
        setErrors(err);
    }
}

Hope this would be helpful for you.

CodePudding user response:

To wait until state has updated its value, there is something called flushSyncwhich can be imported from react-dom

What it does is

flushSync lets you force React to flush any updates inside the provided callback synchronously. This ensures that the DOM is updated immediately.

import {flushSync} from 'react-dom'

// in a component

flushSync(()=>{
setState(value)
})
// The below code will only run the state in `flushSync` has updated. 

  • Related