1

I have a form with two inputs. One for just a text and another for image I select from my local PC.

I want to upload the image to cloudinary and use the text URL to be saved in the uploadedImageURL state and then I use that state to update the form state. I want upload the form state later in MongoDB.

The problem I'm facing with this code is that when I start the server and select the image and type the text and click the Add button, it doesn't change the uploadedImageURL and the form state and in console.log, it returns just an empty string.

But, after clicking the Add button a few times with the same inputs. The states finally updates producing the expected result.

I also noticed that after I change the inputs and click Add, it doesn't change the state and sends the previously selected image to cloudinary.

How can I solve this issue?

I want this code to work the first time I click the Add button with the inputs I selected.

export default function Home() {
  const [form, setForm] = useState({ title: "", classImageURL: "" });
  const [modalShown, setModalShown] = useState(false);
  const [imageSelected, setImageSelected] = useState("");
  const [uploadedImageURL, setUploadedImageURL] = useState("");
  const [classTitle, setClassTitle] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    setModalShown(false);
    await uploadImage()
    setForm({title: classTitle, classImageURL: uploadedImageURL})
    console.log(form)

    // await uploadImage().then(() => {
    //  setForm({ title: classTitle, classImageURL: uploadedImageURL });
    //  console.log("form:", form);
    // });
  };

  const uploadImage = async () => {
    const formData = new FormData();
    formData.append("file", imageSelected);
    formData.append("upload_preset", "cloudinary_default");

    await Axios.post(
      "https://api.cloudinary.com/v1_1/<userid>/image/upload",
      formData
    ).then((response) => {
      setUploadedImageURL(response.data.url);
      console.log(`uploaded image url of cloudinary:`, uploadedImageURL);
    });

  };

  return (
    <div>
      <AddButton modalShown={modalShown} setModalShown={setModalShown} />
      {/* Modal */}
      <div>
        <div
          className={`${
            modalShown ? "" : "hidden"
          } absolute top-1/2 left-1/2 -translate-x-1/2 translate-y-3/4`}
        >
          <form onSubmit={handleSubmit}>
            <div>
              <label>Class Name</label>
              <input
                type="text"
                required
                placeholder="Class Name"
                name="title"
                onChange={(e) => setClassTitle(e.target.value)}
              />
            </div>
            <div>
              <label>Image</label>
              <input
                type="file"
                required
                placeholder="Image"
                name="classImageURL"
                onChange={(e) => setImageSelected(e.target.files[0])}
              />
            </div>
            <div>
              <button onClick={() => setModalShown(false)}>Cancel</button>
              <button onClick={() => console.log("Submit Button Clicked")} type="submit">
                Add
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
}

Nasem
  • 417
  • 2
  • 7
  • 15
  • console.log(form) gets executed befor e setForm({title: classTitle, classImageURL: uploadedImageURL}) finishes executing – Faizal Hussain Jul 31 '22 at 08:06
  • Does this answer your question? [React, state setter not updating the value](https://stackoverflow.com/questions/72557803/react-state-setter-not-updating-the-value) – Youssouf Oumar Jul 31 '22 at 08:06
  • So, if I just use a callback function after setState and in that callback function, try to access my state, will it work the way I want it to? – Nasem Jul 31 '22 at 08:06
  • 1
    You can use a useEffect hook to detect the changes like useEffect(()=>{// code here will get executed when the value of form changes },[form]) – Faizal Hussain Jul 31 '22 at 08:09

1 Answers1

2

States are updated asynchronously so doing console.log after setState or invoking a method which use state just after that won't reflect the new state.

Use the below code which updates following in your code

  1. Return the URL from uploadImage so that it can be used in handleSubmit method.
  2. Use useEffect to show the form to log on console.

   const handleSubmit = async (e) => {
   e.preventDefault();
   setModalShown(false);
   const imageUrl = await uploadImage();
   setForm({title: classTitle, classImageURL: imageUrl });
};
    
const uploadImage = async () => {
    const formData = new FormData();
    formData.append("file", imageSelected);
    formData.append("upload_preset", "cloudinary_default");
    
    const response = await Axios.post(
      "https://api.cloudinary.com/v1_1/<userid>/image/upload",
       formData
     );
    
     setUploadedImageURL(response.data.url);
    
     return response.data.url;
 };

 useEffect(() => { 
   console.log(form)
 }, [form]);
      
user1672994
  • 10,509
  • 1
  • 19
  • 32
  • In uploadImage(), I'm already doing this `setUploadedImageURL(response.data.url);`, So, why do I need to again `return response.data.url`? – Nasem Jul 31 '22 at 08:24
  • @Nasem - as mentioned in answer, *States are updated asynchronously* so a code line after setState won't always reflect the updated state. That's why returning the value to `handleSubmit` make sure to have the latest value to use while setting the `form` object. – user1672994 Jul 31 '22 at 08:30
  • Got it. One more thing, If I want to do another post request which I will to MongoDB to save the text and the URL, where will I do that? Will it be in the useEffect hook or anywhere else? – Nasem Jul 31 '22 at 08:32
  • Yes, you can do in at `useEffect` with dependency on `form` object. – user1672994 Jul 31 '22 at 08:35