You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中Firebase上传时setIsUploading状态不更新的修复方案

解决React Firebase上传时setIsUploading状态不更新的问题

核心问题分析

  1. 状态被同步覆盖
    你在onSubmit里调用setIsUploading(true)后,立刻在函数末尾执行了setIsUploading(false)。由于Firebase上传是异步操作,这行同步代码会在所有上传任务启动前就把状态改回false,导致界面永远看不到加载状态。

  2. 闭包导致的旧状态引用
    上传完成回调里的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 10:25:22