React Native Expo+Firestore报错:undefined is not an object(evaluating '_userDoc.incorrect_answer')
解决Firestore读取数据时的"undefined is not an object(evaluating '_userDoc.incorrect_answer')"错误
错误原因
- 状态更新异步性:调用
setUserDoc(snapshot.data())后,userDoc状态不会立即更新,此时直接传入generateOptionsAndShuffle(userDoc),userDoc仍是初始的undefined。 - 缺乏空值校验:即使后续传入了有效数据,若Firestore文档中
incorrect_answer或correct_answer字段缺失,也会触发同样的错误。
解决方案
- 直接使用快照数据而非状态值:在获取到Firestore快照后,直接用
snapshot.data()调用生成选项的函数,避免依赖异步更新的状态。 - 添加空值保护:在
generateOptionsAndShuffle函数中,先校验传入的文档数据及字段是否存在,防止访问undefined的属性。 - 可选:监听userDoc状态变化:通过
useEffect监听userDoc的更新,当数据就绪后再生成选项。
修正后的代码
import { StyleSheet, Text, TouchableOpacity, View, SafeAreaView, Image, BackHandler } from 'react-native' import { StatusBar } from 'expo-status-bar' import React, { useEffect, useState } from 'react' import { doc, getDoc } from "firebase/firestore"; import { auth, db } from '../config'; const shuffleArray=(array)=> { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } const Quiz = ({navigation}) => { const [isActive, setIsActive] = useState(false) const [userDoc, setUserDoc] = useState(null); const [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); const docu = "question"+2; const myDocs = doc(db, "subject", docu) useEffect(()=>{ getQuiz(); },[]) const getQuiz = async()=>{ setLoading(true); try { const snapshot = await getDoc(myDocs); if(snapshot.exists){ const docData = snapshot.data(); setUserDoc(docData); // 直接使用获取到的文档数据,而非状态中的userDoc setOptions(generateOptionsAndShuffle(docData)); }else{ alert("No Doc FOUND") } } catch (error) { alert(error.message) } finally { setLoading(false); } } const generateOptionsAndShuffle=(_userDoc)=>{ // 添加空值校验,防止字段缺失或数据为空 if(!_userDoc?.incorrect_answer || !_userDoc?.correct_answer){ alert("文档字段缺失"); return []; } let options = [..._userDoc.incorrect_answer]; options.push(_userDoc.correct_answer); shuffleArray(options); console.log(options); return options; } const thisHasPress=()=>{ setIsActive(current => !current); } // 建议改为useState变量,否则组件重渲染会重置值 const [energy, setEnergy] = useState(5); const [score, setScore] = useState(0); return ( <SafeAreaView style={styles.container}> <StatusBar style="dark" /> <View style={styles.headerContainer}> <Text style={styles.headerText}>THIS IS QUIZ</Text> </View> <View style={styles.questionContainer}> <Text style={styles.questionText}>{userDoc?.question || ''}</Text> </View> <View style={styles.buttonChoicesContainer}> <TouchableOpacity style={ isActive ? styles.buttonChoicesSelected : styles.buttonChoices}> <Text style={isActive ? styles.buttonChoicesTextSelected : styles.buttonChoicesText}>{options[0] || ''}</Text> </TouchableOpacity > <TouchableOpacity style={ isActive ? styles.buttonChoicesSelected : styles.buttonChoices} onPress={thisHasPress} > <Text style={isActive ? styles.buttonChoicesTextSelected : styles.buttonChoicesText}>{options[1] || ''}</Text> </TouchableOpacity> <TouchableOpacity style={ isActive ? styles.buttonChoicesSelected : styles.buttonChoices} onPress={thisHasPress} > <Text style={isActive ? styles.buttonChoicesTextSelected : styles.buttonChoicesText}>{options[2] || ''}</Text> </TouchableOpacity> <TouchableOpacity style={ isActive ? styles.buttonChoicesSelected : styles.buttonChoices} onPress={thisHasPress} > <Text style={isActive ? styles.buttonChoicesTextSelected : styles.buttonChoicesText}>{options[3] || ''}</Text> </TouchableOpacity> </View> <View style={styles.footer}> <TouchableOpacity style={styles.footerButton} onPress={()=>{ navigation.navigate("Dashboard") }}> <Image source={require('../assets/home-dark.png')} style={styles.navIcons} resizeMode="contain"/> </TouchableOpacity> <TouchableOpacity style={styles.footerButtonReversed} onPress={()=>{ if(energy !== 0) setEnergy(prev => prev - 5); setScore(prev => prev + 1); navigation.navigate("Result",{ energy, score }); }}> <Text>SUBMIT</Text> </TouchableOpacity> </View> </SafeAreaView> ) } export default Quiz
额外优化说明
- 将
energy和score改为useState状态变量,避免组件重渲染时变量被重置。 - 用
async/await替代then/catch,让异步代码逻辑更清晰。 - 添加
finally块确保加载状态被重置。
内容的提问来源于stack exchange,提问作者akolngi2
相关产品推荐
相关产品推荐

