React异步函数未完成时渲染致子组件报错原因及解决方法
从Firebase数据库读取数据后通过setState存入problem变量,首次渲染时problem为空对象,父组件仅显示硬编码文本(实际problem.xxx取值为undefined但未报错);setState执行后组件重新渲染能正常展示数据。但将problem传递给子组件AdditionalInfo时,出现错误:
AdditionalInfo.jsx:45 Uncaught TypeError: Cannot read properties of undefined (reading 'map')
为什么只有子组件报错?
父组件中直接访问problem.problemNumber、problem.problemTitle这类属性时,当problem是空对象,返回的是undefined。React在渲染DOM时遇到undefined会直接跳过渲染该部分,不会抛出错误。
但子组件中直接对problem.questionTips调用map方法,此时problem.questionTips是undefined——而undefined并不是数组,没有map方法,因此触发了TypeError。
修复方案
提供三种可行的修复方式,按需选择:
1. 初始化时给questionTips设默认空数组
在父组件的useState初始化时,提前给problem的questionTips属性设置空数组,确保首次渲染时该属性是合法的数组类型:
const [problem, setProblem] = useState({ questionTips: [] });
2. 子组件内添加空值保护
在子组件中使用可选链操作符或空值合并,确保调用map前questionTips是数组:
// 方式一:空值合并兜底空数组 {(problem.questionTips || []).map((tip) => { return <Tip key={tip.number} number={tip.number} description={tip.description} /> })} // 方式二:可选链直接短路 {problem.questionTips?.map((tip) => { return <Tip key={tip.number} number={tip.number} description={tip.description} /> })}
注:可选链?.会在左侧值为undefined/null时直接返回undefined,React渲染undefined会跳过这部分,不会报错。
3. 父组件加载完成后再渲染子组件
添加加载状态,等Firebase数据请求完成后再渲染AdditionalInfo,避免空状态传递给子组件:
// 父组件中添加loading状态 const [problem, setProblem] = useState({}); const [loading, setLoading] = useState(true); useEffect(() => { const getProblem = async () => { const problemsRef = collection(db, "problems"); const q = query(problemsRef, where("problemNumber", "==", parseInt(params.id))); const querySnapshot = await getDocs(q); setProblem({...querySnapshot.docs[0].data()}); setLoading(false); // 数据加载完成后更新状态 } getProblem(); }, [params.id]); // 渲染时判断loading状态 {!loading && <AdditionalInfo problem={problem}/>}
内容的提问来源于stack exchange,提问作者EliasFiz

