React中Firebase上传时setIsUploading状态不更新的修复方案
解决React Firebase上传时
setIsUploading状态不更新的问题 核心问题分析
状态被同步覆盖
你在onSubmit里调用setIsUploading(true)后,立刻在函数末尾执行了setIsUploading(false)。由于Firebase上传是异步操作,这行同步代码会在所有上传任务启动前就把状态改回false,导致界面永远看不到加载状态。闭包导致的旧状态引用
上传完成回调里的console.log("Upload Status: " + isUploading)会一直打印false,这是因为回调函数捕获了onSubmit执行时的isUploading初始值,状态更新后的新值无法被捕获。
修改方案
将上传任务包装为Promise,用Promise.all等待所有上传完成后再更新状态,确保状态变化时机正确。
修改后的完整代码
//React import { useState } from "react"; //Firebase import { storage } from "../firebase"; import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; import { auth, db } from "../firebase"; import { serverTimestamp, addDoc, collection } 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() { const user = auth.currentUser; const [isUploading, setIsUploading] = useState(false); const [images, setImages] = useState([]); const [videos, setVideos] = useState([]); // 封装单个图片上传为Promise const uploadImage = (image) => { return new Promise((resolve, reject) => { const storageRef = ref( storage, `images/${user.uid}/${uuidv4()}-${image.name}` ); const uploadTask = uploadBytesResumable(storageRef, image); uploadTask.on( "state_changed", (snapshot) => { const progress = Math.ceil((snapshot.bytesTransferred / snapshot.totalBytes) * 100); console.log(`${image.name}: Upload is ${progress}% done`); switch (snapshot.state) { case "paused": console.log("Upload is paused"); break; case "running": console.log("Upload is running"); break; } }, (error) => { toast.error(error.message); console.log(error.message); reject(error); }, () => { getDownloadURL(uploadTask.snapshot.ref).then(async (downloadURL) => { console.log("File available at", downloadURL); try { // 改用addDoc避免覆盖同一文档,存储到用户专属子集合 await addDoc(collection(db, "images", user.uid, "uploads"), { imageURL: downloadURL, createdAt: serverTimestamp(), user: user.uid, }); toast.success("Your image has been added"); resolve(downloadURL); } catch (error) { console.log(error); toast.error(error.message); reject(error); } }); } ); }); }; const onSubmit = async (e) => { e.preventDefault(); if (videos.length === 0 && images.length === 0) { toast.error("Please upload images and/or videos"); return; } // 开始上传,设置加载状态 setIsUploading(true); try { // 等待所有图片上传完成 if (images.length > 0) { await Promise.all(images.map(uploadImage)); } // 视频上传逻辑可参考图片上传,同样用Promise.all包装 if (videos.length > 0) { // 补充视频上传代码 } } catch (error) { console.error("Upload failed:", error); } finally { // 无论成功失败,最终关闭加载状态 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.toString()}</p> </> )} </> ); }
关键修改说明
- Promise包装上传任务:把单个文件上传逻辑封装成Promise,让我们可以用
Promise.all等待所有上传任务完成。 - 异步onSubmit:标记
onSubmit为async,通过try/catch/finally确保加载状态一定会被重置,避免出现永久加载的情况。 - 移除同步状态重置:只在上传开始时设
isUploading为true,所有任务完成后再设为false。 - 修复Firestore写入逻辑:原代码用
setDoc会覆盖用户的同一个文档,改用addDoc将每个文件存储到用户专属的子集合中,避免数据丢失。
内容的提问来源于stack exchange,提问作者Coolkid
相关产品推荐
相关产品推荐

