如何在React组件加载前完成Firebase数据加载?
解决Firebase异步数据加载与React组件渲染不同步的问题
你的问题核心有两个:一是异步数据加载完成前组件已完成初始渲染,导致map函数遍历未就绪的数据;二是直接修改state数组(songDB.push())不会触发组件重渲染,同时数据加载逻辑存在效率问题。以下是针对性的解决方案:
关键修复点
- 初始化合理默认状态:给
dataBase设置空数组默认值,避免初始渲染时的未定义错误 - 正确更新React状态:使用不可变的方式更新
songDB,通过setSongDB确保状态正确触发重渲染 - 优化异步数据获取:用
Promise.all并行加载所有歌曲数据,一次性更新状态,保证数据顺序和渲染同步 - 清理无效依赖:移除不必要的
count状态,避免无效重复请求
修改后的完整代码
import Navbar from '../../components/navbar/navbar' import './playlist.css' import { useEffect, useState } from 'react' import album from '../../assets/album1.jpeg' import { collection, getDocs, query, where } from 'firebase/firestore' import { db, storage } from '../../config/firebase' import { getDownloadURL, ref } from "firebase/storage"; import { useNavigate, useParams } from 'react-router' import SongDetail from '../../components/song-detail/songDetail' function Playlist() { const navigate = useNavigate(); const { id } = useParams() // 初始化空数组作为默认值,避免undefined判断问题 const [dataBase, setDataBase] = useState([]) const [songDB, setSongDB] = useState([]) // 移除不必要的count状态 const [Url, setUrl] = useState() useEffect(() => { const fetchData = async () => { try { const PlaylistCollectionRef = collection(db, "playlist") const q = query(PlaylistCollectionRef, where("PlaylistId", "==", id)) const data = await getDocs(q) const filteredData = data.docs.map((doc) => ({ ...doc.data(), id: doc.id })) setDataBase(filteredData) if (filteredData.length > 0) { const playlistSongs = filteredData[0].Songs // 使用Promise.all并行加载所有歌曲数据,保证顺序和效率 const songPromises = playlistSongs.map(async (songItem) => { const SongCollectionRef = collection(db, "music") const songQuery = query(SongCollectionRef, where("SongId", "==", songItem)) const songData = await getDocs(songQuery) return songData.docs.map((doc) => ({ ...doc.data(), id: doc.id }))[0] }) // 等待所有歌曲请求完成后,一次性更新state const loadedSongs = await Promise.all(songPromises) setSongDB(loadedSongs) } } catch (error) { console.log(error) } } fetchData() }, [id]) // 只依赖id,当路由参数变化时重新加载数据 return ( <div className="playlist-wrapper" id='playlist-id'> <div className="playlist-navbar"> <Navbar /> </div> <div className="playlist-main"> <div className="playlist-main-section-1"> <div className="playlist-main-section-1-subsection"> <div className="playlist-section-1-back"> <button>BACK</button> </div> <div className="playlist-section-1-front"> <div className="playlist-subsection-images"> <div className="playlist-img-box"> <img src={album} alt="" /> </div> </div> <div className="playlist-subsection-controls"> <div className="playlist-subsection-controls-name"> <h1>{dataBase[0]?.PlaylistName || "MY PLAYLIST"}</h1> </div> <div className="playlist-subsection-controls-control"> <button>Play</button> <button>Add</button> </div> </div> </div> </div> </div> <div className="playlist-main-section-2"> <div className="playlist-playlist-subsection"> {/* 确保dataBase和songDB都加载完成后再渲染 */} {dataBase.length === 0 || songDB.length !== dataBase[0]?.Songs.length ? ( "LOADING" ) : ( dataBase[0].Songs.map((data, i) => ( <SongDetail playlistId={id} albumId={data.albumId} num={i + 1} name={songDB[i]?.SongName} /> )) )} </div> </div> </div> </div> ) } export default Playlist
代码说明
- 状态初始化:
dataBase初始化为空数组,避免初始渲染时dataBase[0]未定义的错误 - 并行数据加载:用
Promise.all一次性请求所有歌曲数据,保证所有数据加载完成后再更新state,避免多次重渲染 - 正确状态更新:直接将加载完成的歌曲数组赋值给
songDB,确保React检测到状态变化并触发重渲染 - 渲染判断逻辑:同时判断
dataBase和songDB的状态,只有当两者都就绪且数据长度匹配时,才渲染歌曲列表,否则显示加载状态 - 清理无效依赖:移除了
count状态和相关依赖,避免不必要的重复请求
内容的提问来源于stack exchange,提问作者Krish Patel
相关产品推荐
相关产品推荐

