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

React中无法将Firebase Storage下载URL存入同一份Firestore文档

问题描述

尝试将多张图片上传至Firebase Storage,并将这些图片的下载URL以字符串数组形式存入同一份Firestore文档,但每张URL都生成了单独的文档。以下是我的ImageUpload.js代码:

import React, { useState } from "react";
import { toast } from "react-toastify";
import { db, storage } from "../firebase.config";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import {collection ,addDoc ,arrayUnion ,FieldValue ,updateDoc} from "firebase/firestore";
import { useNavigate } from "react-router-dom";
import "./style.css";

function ImageUpload() {
  const [allImages, setAllImages] = useState([]);

  function handleAllImage(e) {
    e.preventDefault();
    setAllImages(e.target.files);
    console.log(e.target.files);
  }

  function MutipleUpload(e) {
    e.preventDefault();

    try {
      const docRef = collection(db, "products");

      {
        for (let i = 0; i < allImages.length; i++) {

          const storageRef = ref(storage, `productImages/${allImages[i].name}`);
          const uploadTask = uploadBytesResumable(storageRef, allImages[i], {
            contentType: allImages[i].type,
          });

          uploadTask.on(
            "state_changed",
            (snapshot) => {
              const progress =
                (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
              console.log(progress);
            },
            (error) => toast.error(error.message),
            () => {
              getDownloadURL(uploadTask.snapshot.ref).then(
                async (downloadURL) => {
                  await addDoc(docRef, {
                    imgUrl: arrayUnion(downloadURL),
                  });
                }
              );
            }
          );
        }
      }
      toast.success("product added successfully!");
      
    } catch (error) {
      console.log(error.message);
    }
  }

  return (
    <>
      <input
        type="file"
        multiple="multiple"
        onChange={handleAllImage}
        required
      />
      <button onClick={MutipleUpload} type="submit" className="buy__btn mt-3">
        Add Image
      </button>
    </>
  );
}

export default ImageUpload;
问题原因

核心问题是每次循环都调用addDoc(docRef, { imgUrl: arrayUnion(downloadURL) })——addDoc的作用是在集合中创建新文档,所以每完成一张图片上传,就会生成一个独立的文档,而不是更新同一个文档。

解决方案

修改思路:

  1. 先创建一个空的Firestore文档,拿到该文档的引用
  2. 遍历图片执行上传,每获取到一个下载URL,就用updateDoc更新这个已创建文档的imgUrl数组
  3. 等待所有上传和更新操作完成后,再提示成功

修改后的代码:

import React, { useState } from "react";
import { toast } from "react-toastify";
import { db, storage } from "../firebase.config";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import { collection, addDoc, arrayUnion, updateDoc } from "firebase/firestore";
import "./style.css";

function ImageUpload() {
  const [allImages, setAllImages] = useState([]);

  function handleAllImage(e) {
    e.preventDefault();
    setAllImages(Array.from(e.target.files)); // 将FileList转为数组,方便遍历操作
    console.log(e.target.files);
  }

  async function MutipleUpload(e) {
    e.preventDefault();

    if (allImages.length === 0) {
      toast.error("请选择要上传的图片");
      return;
    }

    try {
      // 1. 先创建一个初始文档,包含空的图片URL数组
      const newProductDoc = await addDoc(collection(db, "products"), {
        imgUrl: []
      });
      const productDocRef = newProductDoc;

      // 2. 生成所有上传任务的Promise,统一等待完成
      const uploadPromises = allImages.map((image) => {
        return new Promise((resolve, reject) => {
          const storageRef = ref(storage, `productImages/${image.name}`);
          const uploadTask = uploadBytesResumable(storageRef, image, {
            contentType: image.type,
          });

          uploadTask.on(
            "state_changed",
            (snapshot) => {
              const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
              console.log(`${image.name} 上传进度:${progress}%`);
            },
            (error) => {
              toast.error(`${image.name} 上传失败:${error.message}`);
              reject(error);
            },
            () => {
              getDownloadURL(uploadTask.snapshot.ref).then(async (downloadURL) => {
                // 更新已创建的文档,将URL添加到数组中
                await updateDoc(productDocRef, {
                  imgUrl: arrayUnion(downloadURL)
                });
                resolve(downloadURL);
              });
            }
          );
        });
      });

      // 等待所有上传和更新操作完成
      await Promise.all(uploadPromises);
      toast.success("产品图片全部添加成功!");
      
    } catch (error) {
      console.log(error.message);
      toast.error("上传流程出错,请重试");
    }
  }

  return (
    <>
      <input
        type="file"
        multiple="multiple"
        onChange={handleAllImage}
        required
      />
      <button onClick={MutipleUpload} type="submit" className="buy__btn mt-3">
        Add Image
      </button>
    </>
  );
}

export default ImageUpload;

关键修改点

  • 提前创建目标文档,后续所有URL都更新到该文档的imgUrl数组
  • 使用Promise.all确保所有上传和更新操作完成后再提示成功,避免提前触发成功提示
  • 将e.target.files转换为数组,解决FileList类数组对象的遍历问题
  • 增加空选择判断,优化用户体验

内容的提问来源于stack exchange,提问作者Aadhil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:02:57