You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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字段缺失,也会触发同样的错误。

解决方案

  1. 直接使用快照数据而非状态值:在获取到Firestore快照后,直接用snapshot.data()调用生成选项的函数,避免依赖异步更新的状态。
  2. 添加空值保护:在generateOptionsAndShuffle函数中,先校验传入的文档数据及字段是否存在,防止访问undefined的属性。
  3. 可选:监听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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 15:10:24