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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:08:14