I am trying to render some fetched data to the DOM from an API which is called in useEffect() hook. I set a boolean "isFetching" to start rendering the data after the fetch process ends, but I get an error for infinite loop instead. The only way that my fetched data appears on render is while I use setTimeout() and wait for the data to be fetched, but this is only for testing...
import "bootstrap/dist/css/bootstrap.css";
import { useEffect } from "react";
import axios from "axios";
export default function App() {
//the array of loadedQuestions that is fetched from the API
let [loadedQuestions, setLoadedQuestions] = React.useState([]);
let [answers, setAnswers] = React.useState([]);
let [isFetching, setIsFetching] = React.useState(true);
async function fetchData() {
const response = await axios.get(
"https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple"
);
console.log(response);
const data = await response.data;
loadedQuestions = data.results;
setLoadedQuestions(loadedQuestions);
//adding the correct answer to incorrect_answers array
for (let obj of loadedQuestions)
obj.incorrect_answers.splice(
Math.floor(Math.random() * 4),
0,
obj.correct_answer
);
setIsFetching(false);
isFetching = false;
console.log(isFetching);
}
useEffect(() => {
fetchData();
}, []);
const [isClicked, setIsClicked] = React.useState(false);
const [currentQuestion, setCurrentQuestion] = React.useState(0);
const [score, setScore] = React.useState(0);
const [finished, setFinished] = React.useState(false);
const [beforeFinish, setBeforeFinish] = React.useState("");
//setTimeout only for testing....
// setTimeout(() => {
if (!isFetching) {
setAnswers(
loadedQuestions[currentQuestion].incorrect_answers.map((element) => (
<li>
<span
key={currentQuestion.toString()}
onClick={() => handleClick(element)}
style={{ cursor: "pointer" }}
className={isClicked ? textColor(element) : "bg"}
>
{element}
</span>
</li>
))
);
setBeforeFinish(
<div>
<h5 className="m-3" style={{ textDecoration: "underline" }}>
{loadedQuestions[currentQuestion].question}
</h5>
<ol style={{ listStyleType: "lower-latin" }}>{answers}</ol>
<button
onClick={() => nextQuestionFunction()}
style={{
backgroundColor: "#0c88fb",
color: "white",
marginLeft: 10,
}}
>
Next Question
</button>
<h5 style={{ marginTop: 15, marginLeft: 10 }}>
Your score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>!
</h5>
</div>
);
}
// }, 2000);
const afterFinish = (
<div>
<h1>Finished!</h1>
<h5>
Your Score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>
</h5>
<button
onClick={() => tryAgain()}
style={{ backgroundColor: "#0c88fb", color: "white", marginLeft: 2 }}
>
Try Again
</button>
</div>
);
function handleClick(element) {
setIsClicked(true);
textColor(element);
if (element === loadedQuestions[currentQuestion].correct_answer) {
setScore(score 100 / loadedQuestions.length);
}
}
function textColor(element) {
let classN = "bg ";
element === loadedQuestions[currentQuestion].correct_answer
? (classN = "bg-info")
: (classN = "bg-secondary");
return classN;
}
function nextQuestionFunction() {
if (currentQuestion 1 === loadedQuestions.length) {
setFinished(true);
} else {
setCurrentQuestion(currentQuestion 1);
setIsClicked(false);
}
}
function textDisplay() {
if (finished) {
return afterFinish;
} else {
return beforeFinish;
}
}
function tryAgain() {
setCurrentQuestion(0);
setScore(0);
setIsClicked(false);
setFinished(false);
}
return textDisplay();
}
CodePudding user response:
if (!isFetching) {
setAnswers(
loadedQuestions[currentQuestion].incorrect_answers.map((element) => (
// etc
))
);
setBeforeFinish(
<div>
// etc
</div>
);
}
You are calling setAnswers
and setBeforeFinish
in the middle of rendering. Setting state causes the component to rerender and when it renders you set state again, which renders again, which sets state again, etc.
These don't look like they should even be states at all. loadedQuestions
and isFetching
are the true state, and then answers
and beforeFinish
are just values calculated from that state. I recommend you delete the answers
and beforeFinish
states, and where you used to have the if (!isFetching)
code you do:
let answers = null;
let beforeFinish = null;
if (!isFetching) {
answers = loadedQuestions[currentQuestion].incorrect_answers.map(
(element) => (
<li>
<span
key={currentQuestion.toString()}
onClick={() => handleClick(element)}
style={{ cursor: "pointer" }}
className={isClicked ? textColor(element) : "bg"}
>
{element}
</span>
</li>
)
);
beforeFinish = (
<div>
<h5 className="m-3" style={{ textDecoration: "underline" }}>
{loadedQuestions[currentQuestion].question}
</h5>
<ol style={{ listStyleType: "lower-latin" }}>{answers}</ol>
<button
onClick={() => nextQuestionFunction()}
style={{
backgroundColor: "#0c88fb",
color: "white",
marginLeft: 10,
}}
>
Next Question
</button>
<h5 style={{ marginTop: 15, marginLeft: 10 }}>
Your score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>!
</h5>
</div>
);
}