React组件异步获取数组后未更新且读取属性报错问题
问题描述
我有一个React组件,通过async-await函数从数据库获取对象数组。数据库能正确返回数据(可通过console.log确认),但把数据设置到State后组件没正常更新,还出现属性读取错误。
组件代码
export default function LevelLeaderboard ({ level }) { const [leaderboardData, setLeaderboardData] = useState([]) useEffect(()=>{ getLeaderBoardData(level) .then((data)=>{ console.log(data) setLeaderboardData(data) }) },[level]); return ( <div id="level" className="level-leaderboard"> <ul> <li>{leaderboardData[0]['name']}</li> </ul> </div> ) }
错误信息
LevelLeaderboard.js:19 Uncaught TypeError: Cannot read properties of undefined (reading '0')
at LevelLeaderboard (LevelLeaderboard.js:19:1)
at renderWithHooks (react-dom.development.js:16305:1)
at mountIndeterminateComponent (react-dom.development.js:20074:1)
at beginWork (react-dom.development.js:21587:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
at beginWork$1 (react-dom.development.js:27451:1)
at performUnitOfWork (react-dom.development.js:26557:1)
at workLoopSync (react-dom.development.js:26466:1)
异步函数代码
export async function getLeaderBoardData(level, handleData){ const querySnapshot = await getDocs(collection(leaderboard, level)); let tempArr = []; querySnapshot.forEach((doc) => { tempArr.push(doc.data()) }); return tempArr; }
问题根源
错误的核心是初始渲染时数据还未加载完成:
- 组件挂载时,
leaderboardData的初始值是空数组[] - 此时直接访问
leaderboardData[0]['name'],leaderboardData[0]会返回undefined,再读取name属性就触发TypeError - 虽然异步请求完成后会更新state,但初始渲染的错误已经导致组件崩溃
解决方法
1. 条件渲染(推荐)
先判断数组是否有数据,再渲染对应内容,同时可以添加加载提示:
export default function LevelLeaderboard ({ level }) { const [leaderboardData, setLeaderboardData] = useState([]) useEffect(()=>{ getLeaderBoardData(level) .then((data)=>{ console.log(data) setLeaderboardData(data) }) },[level]); return ( <div id="level" className="level-leaderboard"> <ul> {leaderboardData.length > 0 ? ( <li>{leaderboardData[0]['name']}</li> ) : ( <li>加载中...</li> )} </ul> </div> ) }
2. 使用可选链操作符(?.)
通过?.语法避免访问undefined的属性,同时可以设置默认值:
export default function LevelLeaderboard ({ level }) { const [leaderboardData, setLeaderboardData] = useState([]) useEffect(()=>{ getLeaderBoardData(level) .then((data)=>{ console.log(data) setLeaderboardData(data) }) },[level]); return ( <div id="level" className="level-leaderboard"> <ul> <li>{leaderboardData[0]?.['name'] || '加载中...'}</li> </ul> </div> ) }
3. 提前处理空数组边界
如果数据库可能返回空数组,可以在组件顶部提前判断并返回默认内容:
export default function LevelLeaderboard ({ level }) { const [leaderboardData, setLeaderboardData] = useState([]) useEffect(()=>{ getLeaderBoardData(level) .then((data)=>{ console.log(data) setLeaderboardData(data) }) },[level]); if (leaderboardData.length === 0) { return <div className="level-leaderboard">暂无数据</div> } return ( <div id="level" className="level-leaderboard"> <ul> <li>{leaderboardData[0]['name']}</li> </ul> </div> ) }
内容的提问来源于stack exchange,提问作者TypeKG

