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的作用是在集合中创建新文档,所以每完成一张图片上传,就会生成一个独立的文档,而不是更新同一个文档。
解决方案
修改思路:
- 先创建一个空的Firestore文档,拿到该文档的引用
- 遍历图片执行上传,每获取到一个下载URL,就用
updateDoc更新这个已创建文档的imgUrl数组 - 等待所有上传和更新操作完成后,再提示成功
修改后的代码:
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
相关产品推荐
相关产品推荐

