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>提示,之后才渲染酒店列表组件,想请教问题原因及解决办法。
问题原因
- 初始状态触发错误渲染分支:组件初始化时
hotels是空数组[],isLoading初始值为false,第一次渲染直接进入hotels.length === 0的分支,显示无酒店提示。 - 异步回调执行时机问题:
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
相关产品推荐
相关产品推荐

