Uncaught TypeError: "Cannot read properties of undefined" in react native

I have a React native function Quiz with them where I make a fetch GET request. I have a check if the data has loaded with the showQuestion variable. But somehow it, when the data has loaded the app, returns this error message:

Cannot read properties of undefined (evaluating 'questObj[currentQuestion].questionText')

I tried to print the data questObj[currentQuestion] and that gives me an object which is filled with data. The image down below is the result of console.log.

  console.log(questObj[currentQuestion]);

image of console.log

Any suggestions on what I could be doing wrong? (I'm new to React native so it could probably be something I'm doing completely wrong.)

export function Quiz(this: any) {
  const styles = StyleSheet.create({
    container: {
      backgroundColor: '#B86566',
      width: '100%',
      height: '100%',
      alignItems: 'center',
      justifyContent: 'center'
    },
    headingColumn: {
      flexBasis: '90%',
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      flex: 1,
      padding: 20,
      fontSize: 30,
    },
    buttonColumn: {
      flexBasis: '35%',
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      flex: 1,
      padding: 20,
      margin: 20,
      borderColor: 'white',
      borderWidth: 1
    },
    row: {
      display: 'flex',
      flexDirection: 'row',
      flexWrap: 'wrap',
      width: '100%'
    },
  });
  const [showScore, setShowScore] = useState(false);
  const [showQuestion, setShowQuestion] = useState(false);
  const [currentQuestion, setCurrentQuestion] = useState(0);
  const [score, setScore] = useState(0);
  const [questionArray, setQuestions] = useState(0);

  useEffect(() => {
    getQuestions();
  }, []);

  let questObj: {
    questionText: any;
    answerOptions: any;
  }[] = [];

  const getQuestions = async () => {
    try {
      let response = await fetch(
        'https://opentdb.com/api.php?amount=10&category=26&difficulty=medium&type=multiple'
      );
      let json = await response.json();
      json.results.forEach((element: { question: any; correct_answer: any; incorrect_answers: any; }) => {
        questObj.push({
          questionText: element.question,
          answerOptions: [{ answerText: element.correct_answer, isCorrect: true },
          { answerText: element.incorrect_answers[0], isCorrect: false },
          { answerText: element.incorrect_answers[1], isCorrect: false },
          { answerText: element.incorrect_answers[2], isCorrect: false }
          ],
        },
        );
        console.log(questObj[currentQuestion]);
        setShowQuestion(true);
      });

    } catch (error) {
      console.error(error);
    }

  }

  const handleAnswerButtonClick = (isCorrect: boolean) => {
    if (isCorrect) {
      setScore(score + 1);
    }
    const nextQuestion = currentQuestion + 1;
    if (nextQuestion < questObj.length) {
      setCurrentQuestion(nextQuestion);
    } else {
      setShowScore(true);
    }
  };

  return <View style={styles.container}>
    <View><Text></Text></View>
    <View>{showScore ?
      <Text>You scored {score} out of {questObj.length}</Text>
      : <></>}
    </View>
    {showQuestion ?
      <View>

        <View >
          <Text>Question {currentQuestion + 1}/{questObj.length} </Text>
        </View>

        <View style={styles.row}>
          <Text style={Object.assign(styles.headingColumn)}>{questObj[currentQuestion].questionText}</Text>
        </View>
        <View style={styles.row}>
          {questObj[currentQuestion].answerOptions.map((answerOption: { answerText: string; isCorrect: boolean; }, index: any) => (
            <Text style={Object.assign(styles.buttonColumn)}>
              <Button
                title={answerOption.answerText}
                onPress={() => handleAnswerButtonClick(answerOption.isCorrect)}
                color="#fff">
              </Button>
            </Text>
          ))}
        </View>
      </View>
      : <></>}
  </View>;
}

Solution 1:

When using react, you never want to grab data from a source and manually try to control the state. That's what useState() is for :).

Any kind of data that you are dealing with (for the most part with a few exceptions) should all be handled and maintained with some sort of useState() hook.

By not using useState(), React will not know that the data you are wanting to render in the dom has been updated with actual data!

So when you call questObj[currentQuestion].questionText inside of the JSX, react sees no data (since you only pushed data onto an array and didn't trigger a proper re-render), and says "HEY, there is no data here as an object".

Also, be sure that you are properly gathering the right state for choosing the currentQuestion. If you are only choosing one, you have to loop through an array of objects to then identify an object key that matches your current question... unless you are simply going off of array indexes, then it should be fine I suppose!

Good Luck!

Below is a fixing of the useState(), or lack there of.

export function Quiz(this: any) {
  const styles = StyleSheet.create({
    container: {
      backgroundColor: "#B86566",
      width: "100%",
      height: "100%",
      alignItems: "center",
      justifyContent: "center",
    },
    headingColumn: {
      flexBasis: "90%",
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
      flex: 1,
      padding: 20,
      fontSize: 30,
    },
    buttonColumn: {
      flexBasis: "35%",
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
      flex: 1,
      padding: 20,
      margin: 20,
      borderColor: "white",
      borderWidth: 1,
    },
    row: {
      display: "flex",
      flexDirection: "row",
      flexWrap: "wrap",
      width: "100%",
    },
  });
  const [showScore, setShowScore] = useState(false);
  const [showQuestion, setShowQuestion] = useState(false);
  const [currentQuestion, setCurrentQuestion] = useState(0);
  const [score, setScore] = useState(0);
  const [questionArray, setQuestions] = useState(0);

  useEffect(() => {
    getQuestions();
  }, []);

  //   let questObj: {
  //     questionText: any;
  //     answerOptions: any;
  //   }[] = [];

  // you need to use Use state for any state that you are holding! That's the whole point of react!
  const [questObjState, setQuestObjState] = useState([]);

  const getQuestions = async() => {


    try {
      let response = await fetch(
        "https://opentdb.com/api.php?amount=10&category=26&difficulty=medium&type=multiple"
      );
      let json = await response.json();
      // use map here to return new array rather than just opertate
      const newArrayOfData = json.results.map(
        (element: {
          question: any,
          correct_answer: any,
          incorrect_answers: any,
        }) => {
          return {
            questionText: element.question,
            answerOptions: [{
                answerText: element.correct_answer,
                isCorrect: true,
              },
              {
                answerText: element.incorrect_answers[0],
                isCorrect: false,
              },
              {
                answerText: element.incorrect_answers[1],
                isCorrect: false,
              },
              {
                answerText: element.incorrect_answers[2],
                isCorrect: false,
              },
            ],
          };
          // questObj.push({
          //     questionText: element.question,
          //     answerOptions: [
          //         {
          //             answerText: element.correct_answer,
          //             isCorrect: true,
          //         },
          //         {
          //             answerText: element.incorrect_answers[0],
          //             isCorrect: false,
          //         },
          //         {
          //             answerText: element.incorrect_answers[1],
          //             isCorrect: false,
          //         },
          //         {
          //             answerText: element.incorrect_answers[2],
          //             isCorrect: false,
          //         },
          //     ],
          // });
          // console.log(questObj[currentQuestion]);
          // setShowQuestion(true);
        },

        setQuestObjState(newArrayOfData)
      );
    } catch (error) {
      console.error(error);
    }
  };

  const handleAnswerButtonClick = (isCorrect: boolean) => {
    if (isCorrect) {
      setScore(score + 1);
    }
    const nextQuestion = currentQuestion + 1;
    if (nextQuestion < questObj.length) {
      setCurrentQuestion(nextQuestion);
    } else {
      setShowScore(true);
    }
  };

  return ( <
    View style = {
      styles.container
    } >
    <
    View >
    <
    Text > < /Text> <
    /View> <
    View > {
      showScore ? ( <
        Text >
        You scored {
          score
        }
        out of {
          questObj.length
        } <
        /Text>
      ) : ( <
        > < />
      )
    } <
    /View> {
      showQuestion ? ( <
        View >
        <
        View >
        <
        Text >
        Question {
          currentQuestion + 1
        }
        /{questObj.length}{" "} <
        /Text> <
        /View>

        <
        View style = {
          styles.row
        } >
        <
        Text style = {
          Object.assign(styles.headingColumn)
        } > {
          questObj[currentQuestion].questionText
        } <
        /Text> <
        /View> <
        View style = {
          styles.row
        } > {
          questObj[currentQuestion].answerOptions.map(
            (
              answerOption: {
                answerText: string,
                isCorrect: boolean,
              },
              index: any
            ) => ( <
              Text style = {
                Object.assign(styles.buttonColumn)
              } >
              <
              Button title = {
                answerOption.answerText
              }
              onPress = {
                () =>
                handleAnswerButtonClick(
                  answerOption.isCorrect
                )
              }
              color = "#fff" >
              < /Button> <
              /Text>
            )
          )
        } <
        /View> <
        /View>
      ) : ( <
        > < />
      )
    } <
    /View>
  );
}
https://stackoverflow.com/questions/70712396/error-undefined-is-not-an-object-in-react-native#