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:
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.
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.