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

React组件加载时先显示无酒店提示再渲染列表的问题求助

问题:React酒店列表组件加载时先显示"无酒店"提示再渲染列表

我编写了一个用于渲染酒店信息列表的React组件,代码如下:

import {useEffect, useState} from "react";
import {onSnapshot} from "firebase/firestore"
import {hotelCollection} from "../controller/controller";
import {HotelType} from "../types/hotel";
import Information from "../components/Information";

function Card() {

    const [isLoading, setIsLoading] = useState(false);
    const [hotels, setHotels] = useState<HotelType[]>([]);

    useEffect(() => onSnapshot(hotelCollection, (snapshot) => {
        setIsLoading(true);
        setHotels(
            snapshot.docs.map((doc) => {
                return {
                    id: doc.id,
                    ...doc.data(),
                }
            })
        )
        setIsLoading(false);
    }), []);

    if (isLoading) return <div className="loading"/>;
    return (
        <div className="card">
            <h2 className="title">All hotels</h2>
            {hotels && hotels.length ? (
                <div>
                    {
                        hotels?.map((hotel) => (
                            <Information key= {hotel.id} hotel={hotel}/>
                        ))
                    }
                </div>
            ): (
                <h2 className="no-hotels">There isn't any hotel, please add one.</h2>
            )}
        </div>
    )
}

export default Card;

该组件功能正常,但进入页面或通过路由访问时,会先显示<h2 className="no-hotels">There isn't any hotel, please add one.</h2>提示,之后才渲染酒店列表组件,想请教问题原因及解决办法。


问题原因

  1. 初始状态触发错误渲染分支:组件初始化时hotels是空数组[],isLoading初始值为false,第一次渲染直接进入hotels.length === 0的分支,显示无酒店提示。
  2. 异步回调执行时机问题:onSnapshot的回调是异步触发的,即便在回调内先调用setIsLoading(true),但React状态更新是批量处理的,初始渲染已经完成,无法在数据请求过程中切换到加载状态,直到回调执行完毕更新hotels后才会渲染列表。

解决办法

方案1:调整初始加载状态

将isLoading的初始值设为true,确保组件挂载后直接显示加载状态,直到Firestore快照数据返回后再切换状态:

import {useEffect, useState} from "react";
import {onSnapshot} from "firebase/firestore"
import {hotelCollection} from "../controller/controller";
import {HotelType} from "../types/hotel";
import Information from "../components/Information";

function Card() {
    // 初始设为true,进入页面直接显示加载
    const [isLoading, setIsLoading] = useState(true);
    const [hotels, setHotels] = useState<HotelType[]>([]);

    useEffect(() => onSnapshot(hotelCollection, (snapshot) => {
        setHotels(
            snapshot.docs.map((doc) => ({
                id: doc.id,
                ...doc.data(),
            }))
        );
        // 数据加载完成后关闭加载状态
        setIsLoading(false);
    }), []);

    if (isLoading) return <div className="loading"/>;
    
    return (
        <div className="card">
            <h2 className="title">All hotels</h2>
            {hotels.length ? (
                <div>
                    {hotels.map((hotel) => (
                        <Information key={hotel.id} hotel={hotel}/>
                    ))}
                </div>
            ): (
                <h2 className="no-hotels">There isn't any hotel, please add one.</h2>
            )}
        </div>
    )
}

export default Card;

方案2:增加"未初始化"状态区分(可选)

如果需要更精确区分"未开始加载"、"加载中"、"加载完成"状态,可以额外添加状态变量,避免初始空数组被误判为无数据:

import {useEffect, useState} from "react";
import {onSnapshot} from "firebase/firestore"
import {hotelCollection} from "../controller/controller";
import {HotelType} from "../types/hotel";
import Information from "../components/Information";

function Card() {
    const [isLoading, setIsLoading] = useState(false);
    const [hotels, setHotels] = useState<HotelType[]>([]);
    // 标记是否已经完成第一次数据加载
    const [isInitialized, setIsInitialized] = useState(false);

    useEffect(() => {
        setIsLoading(true);
        const unsubscribe = onSnapshot(hotelCollection, (snapshot) => {
            setHotels(
                snapshot.docs.map((doc) => ({
                    id: doc.id,
                    ...doc.data(),
                }))
            );
            setIsLoading(false);
            setIsInitialized(true);
        });
        // 清理Firestore订阅
        return unsubscribe;
    }, []);

    if (isLoading) return <div className="loading"/>;
    
    return (
        <div className="card">
            <h2 className="title">All hotels</h2>
            {!isInitialized ? null : (
                hotels.length ? (
                    <div>
                        {hotels.map((hotel) => (
                            <Information key={hotel.id} hotel={hotel}/>
                        ))}
                    </div>
                ): (
                    <h2 className="no-hotels">There isn't any hotel, please add one.</h2>
                )
            )}
        </div>
    )
}

export default Card;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:01:17