Home > Mobile >  React limits the number of renders to prevent an infinite loop error, while using empty array depend
React limits the number of renders to prevent an infinite loop error, while using empty array depend

Time:10-03

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>
  );
}

  • Related