Home > Net >  How do i fix setUseState not updating "uploading" state during firebase upload?
How do i fix setUseState not updating "uploading" state during firebase upload?

Time:02-04

I'm new to react and Firebase. I'm building an app where users can upload several big images(up to 3MB) and videos to Firebase storage and then reference url in Firestore. Both are submitted with one onSubmit function. Everything else works, as in, users are able to drop, see which images are accepted, press submit and the images are uploaded to Firebase storage and Firestore.

Its just that the setIsUploading(true) does not work once submit button is pressed. All my console.logs come back false throughout the code.

While trying to find a fix, i learnt that useState is asynchronous and useEffect would resolve this but i don't know how to put it in the code and what would be its dependency/dependencies.


    //React
    import { useEffect, useState } from "react";
    
    //Firebase
    import { storage } from "../firebase";
    import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
    import { auth, db } from "../firebase";
    import { serverTimestamp, setDoc, doc } from "firebase/firestore";
    
    //imports from video and image from components
    import ImageUpload from "../components/portfolioComponents/ImageUpload";
    import VideoUpload from "../components/portfolioComponents/VideoUpload";
    
    //Others
    import { toast } from "react-toastify";
    import { v4 as uuidv4 } from "uuid";
    import Loading from "../components/Loading";
    
    export default function PortfolioEdit() {
      //identify current user
      const user = auth.currentUser;
    
      const [isUploading, setIsUploading] = useState(false);
      const [images, setImages] = useState([]);
      const [videos, setVideos] = useState([]);
    
      const onSubmit = (e) => {
        e.preventDefault();
    
        setIsUploading(true)
    
        if (videos.length === 0 && images.length === 0) {
          toast.error("Please upload images and/or videos");
    return
    
        }
        if (images.length > 0) {
    
          images.map((image) => {
            setIsUploading(true);
            const storageRef = ref(
              storage,
              "images/"   user.uid   "/"   uuidv4()   "-"   image.name
            );
    
            const uploadTask = uploadBytesResumable(storageRef, image);
    
            uploadTask.on(
              "state_changed",
              (snapshot) => {
                // Observe state change events such as progress, pause, and resume
                // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
                const progress =
                  (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
                console.log(
                  image.name   ": Upload is "   Math.ceil(progress)   "% done"
                );
                switch (snapshot.state) {
                  case "paused":
                    console.log("Upload is paused");
                    break;
                  case "running":
                    console.log("Upload is running");
                    break;
                }
              },
              (error) => {
                // Handle unsuccessful uploads
                toast.error(error.message);
                console.log(error.message);
              },
              () => {
                getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
                  console.log("File available at", downloadURL);
                  console.log(user.uid);
                  console.log("Upload Status: "   isUploading);
    
                  try {
                    setDoc(doc(db, "images/"   user.uid), {
                      imageURL: downloadURL,
                      createdAt: serverTimestamp(),
                      user: user.uid,
                    });
                    toast.success("Your image has been added");
                  } catch (error) {
                    console.log(error);
                    toast.error(error.message);
                  }
                });
              }
            );
          });
        }
    setIsUploading(false)
      };
    
      return (
        <>
          {isUploading ? (
            <Loading />
          ) : (
            <>
              <h1 className="text-center mt-10">
                Add Images and Videos to your portfolio
              </h1>
              <form onSubmit={onSubmit}>
                <ImageUpload setImages={setImages} />
                <VideoUpload setVideos={setVideos} />
    
                <button className="block mx-auto mt-4 p-4 border-2 border-black">
                  Upload Images
                </button>
              </form>
              <p>Loading status: {isUploading}</p>
            </>
          )}
        </>
      );
    }

I would really appreciate this. I've been struggling with this for a while.

CodePudding user response:

Well while the setIsUploading is async operation, it's really fast and it doesn't look like the issue here.

I think your issue is that you are not waiting for anything before setting the isUploading back to false (so you are not waiting for any of the uploads to finish, which are async).

You basically need to wait for all async operations to end if you want your isUploading to properly reflect the state of uploading images. One way to accomplish this is something like:

const onSubmit = async (e) => {
  e.preventDefault()
  setIsUploading(true)

  // 1. Create an array to put promises to await for
  const imageLoadingPromises = []
  ...

  if (images.length > 0) {
    images.map((image) => {
      // 2. Create a promise for each image upload
      const uploadPromise = new Promise((resolve) => {
        const storageRef = ref(
          storage,
          "images/"   user.uid   "/"   uuidv4()   "-"   image.name
        )

        const uploadTask = uploadBytesResumable(storageRef, image)

        uploadTask.on(
          "state_changed",
          (snapshot) => {
            const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100

            switch (snapshot.state) {
              ...
            }
          },
          (error) => {
            toast.error(error.message)
          },
          () => {
            getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
              try {
                setDoc(doc(db, "images/"   user.uid), {
                  imageURL: downloadURL,
                  createdAt: serverTimestamp(),
                  user: user.uid,
                })
                toast.success("Your image has been added")
              } catch (error) {
                toast.error(error.message)
              }
              // 3. resolve the promise once the loading is done
              resolve()
            })
          }
        )
      })
      // 4. Add the promise to the array imageLoadingPromises array
      imageLoadingPromises.push(uploadPromise)
    })
  }
  // 5. Wait for all images to be loaded before setting the isUploading back to false
  await Promise.all(imageLoadingPromises)
  setIsUploading(false)
}

So basically adding the 5 steps mentioned above it your code should help you fixing your issue. Please note that this is a proof of concept. So if there is a case where you don't get to the resolve(), or you encounter other minor issues you might need to adapt it to your needs.

TIPS:

  1. you don't really need setIsUploading(true); inside images.map since it should already be set to true at the beginning of the function

  2. Also splitting your long onSubmit function into smaller, meaningful named functions would help a lot.

CodePudding user response:

Set it to true as soon as your function initiates but than you have to set it false at uploadTask on callback.

Since that callback is also async is just finishing later than the false setter.

  • Related