React+Firestore数据加载异常:初始不渲染,仅保存文件后显示
问题描述
从Firebase获取对象及对应文件夹内的图片,打包为包含所有图片链接的对象后设置状态并渲染。但页面初始时无法渲染内容,仅在保存Overview组件文件后才会显示。本人是React和Firebase新手,尝试过多种方法但已遗忘,不知如何解决。
相关代码
数据获取并传递给Overview的页面
import React, { useEffect, useState } from "react"; import Overview from "../../Components/DisplayOverview/Overview"; import { getJourney } from "../../Config/firestore"; export default function ExplorePage() { const [overviewInfo, setOverviewInfo] = useState([]); const [isLoaded, setIsLoaded] = useState(false) useEffect(() => { const plonk = async () => { setOverviewInfo(await getJourney()) } plonk() console.log(overviewInfo) },[]) return ( <div className="explore-cont"> {overviewInfo.map((x) => ( <Overview key={crypto.randomUUID()} im={x.imageURL} name={x.name} location={x.location} description={x.description} photoURL={x.photoURL} userName={x.userName} price={x.price} /> ))} </div> ); }
Overview渲染组件
import React, { useEffect, useState } from "react"; import "react-responsive-carousel/lib/styles/carousel.min.css"; import { Carousel } from "react-responsive-carousel"; import "./overview.css"; export default function Overview(props) { const [iamges, setImages] = useState([]) useEffect(()=>{ setImages(props.im) },[props]) return ( <div className="container"> <div className="image-cont"> <Carousel autoPlay={true} infiniteLoop={true} showThumbs={false} showIndicators={false} showStatus={false} width={200} useKeyboardArrows={false} centerMode={true} autoFocus={false} > {iamges.map(a=> { return( <div key={crypto.randomUUID()}> <img src={a} /> </div> ) })} </Carousel> </div> <div className="words-cont"> <span className="otitle">{props.name}</span> <span className="location">{props.location}</span> <span className="description">{props.description}</span> <div className="person-info"> <img className="image" src={props.photoURL} /> <span className="username">{props.userName}</span> <span className="price">£{props.price}</span> </div> </div> </div> ); }
Firebase数据获取代码
import { addDoc, collection, getDocs } from "firebase/firestore"; import { db } from './firebase' import { ref } from "firebase/storage"; import { retrieveImages } from "./storage"; const JOURNEYDATA = `journeyData` export async function addJourneyData(uid,location,name,description,price, uuid, username,photoURL){ const myDocRef = addDoc(collection(db,JOURNEYDATA), { uid: uid, name: name, location: location, description: description, price: price, imageBucket: `/JourneyImages/${uid}/${uuid}/overview`, userName: username, photoURL: photoURL }) return myDocRef } export async function addActivityData(activityName, description, price, imageBucket , docRef,n){ addDoc(collection(db,`${docRef.path}/acivity`),{ activityNumber: n, activityName: activityName, description: description, price: price, imageBucket: imageBucket }) } export async function getJourney(){ const querySnapshot = getDocs(collection(db,JOURNEYDATA)) let allData = [] for(const documentSnapshot of (await querySnapshot).docs){ const data = documentSnapshot.data() await allData.push({ ...data, id: documentSnapshot.id, imageURL: await retrieveImages(data['imageBucket']) }) } return allData }
Firebase存储图片获取代码
import { ref, uploadBytes, getDownloadURL, listAll } from "firebase/storage" import { storage } from './firebase' export async function uploadImages(images, uid, projectName, overview){ for(let i = 0; i < images.length; i++){ let bucket = `/JourneyImages/${uid}/${projectName}/${overview}/${images[i].name}` const imageRef = ref(storage, bucket) await uploadBytes(imageRef, images[i]) .then(() => console.log('success')) .catch((err) => console.log(err)) } } export async function retrieveImages(bucket){ let imageList = [] await listAll(ref(storage, bucket)).then((res) => res.items.forEach(element => { getDownloadURL(ref(storage , element.fullPath)).then(url => { imageList.push(url) }) })) return imageList }
问题原因与解决方案
核心问题
问题出在retrieveImages函数的异步处理逻辑:虽然listAll是异步等待的,但遍历文件项时,getDownloadURL通过.then异步执行,没有等待所有请求完成就返回了空的imageList。初始渲染时组件拿到空数组,导致轮播图无内容;保存文件触发热重载时,之前的异步请求可能已完成并缓存了URL,所以能正常显示。
修复步骤
1. 修复图片URL获取函数
改用Promise.all等待所有图片URL请求完成:
export async function retrieveImages(bucket){ const storageRef = ref(storage, bucket); const res = await listAll(storageRef); // 用map生成所有请求的Promise,等待全部完成后返回结果数组 const imageList = await Promise.all( res.items.map(element => getDownloadURL(ref(storage, element.fullPath))) ); return imageList; }
2. 简化Overview组件状态
无需将props.im存入组件内部state,直接使用props即可,减少不必要的状态更新:
import React from "react"; import "react-responsive-carousel/lib/styles/carousel.min.css"; import { Carousel } from "react-responsive-carousel"; import "./overview.css"; export default function Overview(props) { return ( <div className="container"> <div className="image-cont"> <Carousel autoPlay={true} infiniteLoop={true} showThumbs={false} showIndicators={false} showStatus={false} width={200} useKeyboardArrows={false} centerMode={true} autoFocus={false} > {props.im.map((a, index) => ( <div key={index}> <img src={a} alt="旅程图片" /> </div> ))} </Carousel> </div> <div className="words-cont"> <span className="otitle">{props.name}</span> <span className="location">{props.location}</span> <span className="description">{props.description}</span> <div className="person-info"> <img className="image" src={props.photoURL} alt="用户头像" /> <span className="username">{props.userName}</span> <span className="price">£{props.price}</span> </div> </div> </div> ); }
3. 优化页面加载状态(可选)
添加加载提示,避免初始页面空白:
import React, { useEffect, useState } from "react"; import Overview from "../../Components/DisplayOverview/Overview"; import { getJourney } from "../../Config/firestore"; export default function ExplorePage() { const [overviewInfo, setOverviewInfo] = useState([]); const [isLoaded, setIsLoaded] = useState(false); useEffect(() => { const fetchData = async () => { const data = await getJourney(); setOverviewInfo(data); setIsLoaded(true); }; fetchData(); }, []); if (!isLoaded) { return <div>加载中...</div>; } return ( <div className="explore-cont"> {overviewInfo.map((x) => ( <Overview key={x.id} // 用文档ID作为key,比随机UUID更稳定 im={x.imageURL} name={x.name} location={x.location} description={x.description} photoURL={x.photoURL} userName={x.userName} price={x.price} /> ))} </div> ); }
内容的提问来源于stack exchange,提问作者BRRE
相关产品推荐
相关产品推荐

