As discussed in the comments, the issue is that when the component renders for the first time, this.state.questions
is an empty array as set in the state's initial state.
Because of this, this.state.questions[0]
returns undefined, until the API response finishes (which is started by getRandomizedQuestions()
which is called by componentDidMount
after the component has been rendered and mounted).
You need to handle this case. The way to solve it depends on how you want to solve it and what you want to show to the user while it's loading. Here's a few solutions:
1. Just show "loading" until the array isn't empty.
render() {
if (this.state.questions.length < 1) {
return <div className="quiz-window">Loading...</div>;
}
return (
<div className="quiz-window">
// ...
</div>
);
}
2. Use another ternary to show "loading" in the place of the block of JSX which depends on the array:
render() {
return (
<div className="quiz-window">
{this.state.showScore ? (
<div className="score-section">
korrekt beantwortet: {this.state.score} von{" "}
{this.state.questions.length}
</div>
) : this.state.questions.length > 0 ? (
<>
<div className="question-section">
<div className="question-count">
<span>Frage {this.state.currentQuestion + 1}</span>/
{this.state.questions.length}
</div>
<div className="question-text">
{this.state.questions[this.state.currentQuestion].title}
</div>
</div>
<div className="answer-section">
{this.state.questions[this.state.currentQuestion].answers.map(
(answer) => (
<button
onClick={() => this.handleAnswerOptionClick(answer.isCorrect)}
>
{answer.title}
</button>
)
)}
</div>
</>
) : (
<p>Loading</p>
)}
</div>
);
}
Alternatively, if it's possible that the API returns an empty array and you want to handle that as a separate case:
3. Introduce state variable that tracks when the data is loading and show loading until it's done
class Play_quiz extends React.Component {
state = {
currentQuestion: 0,
showScore: false,
score: 0,
questions: [],
isLoading: true,
};
// ...
getRandomizedQuestions = () => {
const apiUrl = "http://localhost:3001/questions";
fetch(apiUrl)
.then((response) => response.json())
.then(
(result) => {
console.log("From database:");
console.log(result);
let amountOfQuestions = result.length;
let randomizedResult = [];
for (let i = 0; i < amountOfQuestions; i++) {
let randomIndex = Math.floor(Math.random() * result.length);
randomizedResult.push(result[randomIndex]);
result.splice(randomIndex, 1);
}
this.setState({
questions: randomizedResult,
isLoading: false
});
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
},
(error) => {
console.log("An unexpected error occurred", error);
}
);
};
// ...
render() {
if (this.state.isLoading) {
return <div className="quiz-window">Loading...</div>;
}
return (
<div className="quiz-window">
// ...
</div>
);
}
}
See also: Why calling react setState method doesn't mutate the state immediately?