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.
2
Answers
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: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:
you don’t really need
setIsUploading(true);
inside images.map since it should already be set to true at the beginning of the functionAlso splitting your long onSubmit function into smaller, meaningful named functions would help a lot.
Set it to
true
as soon as your function initiates but than you have to set it false atuploadTask
on
callback.Since that callback is also async is just finishing later than the
false
setter.