My button needs to be clicked twice to render the new component for the first click React

Viewed 80

I am working on a project that fetchs data from the open Trivia api. I have developed everything already and everything seems to work fine so far. However, when I first render the app, the first time I click the button to submit my answer it calls the function, but doesn't render the new question, answer choices... In order to render I have to click to choose the answer again and click the button again. Once it has rendered after clicking the check answer button for the second time, the rest of the app works perfectly at first click. I am not completely sure what is causing this bug, could you please help me?

Here it is my code:

  import React, { useEffect, useState } from "react";
import { Container, Row, Card, Button, Alert } from "react-bootstrap";
import axios from "axios";
import "../App.css";
import Results from "./Results";

export const createMarkup = (text) => {
  return { __html: text };
};
const QuizCategory = () => {
  const [quizData, setquizData] = useState([]);
  const [category, setCategory] = useState([]);
  const [question, setQuestion] = useState([]);
  const [options, setOptions] = useState([]);
  const [questionsData, setQuestionsData] = useState([]);
  const [questionNumber, setQuestionNumber] = useState(0);
  const [selectedAnswer, setSelectedAnswer] = useState();
  const [correctAnswer, setCorrectAnswer] = useState();
  const [score, setScore] = useState(0);
  const [prevSelectedElement, setPrevSelectedElement] = useState();
  const [quizState, setQuizState] = useState("start");
  const [showAlert, setShowAlert] = useState(false);
  const [rightAlert, setRightAlert] = useState(false);
  const [wrongAlert, setWrongAlert] = useState(false);
  const totalQuestion = 10;

  const fetchQuizData = async () => {
    const { data } = await axios.get("https://opentdb.com/api.php?amount=10");
    setQuestionsData(data);
    console.log(data);
    setCategory(data.results[questionNumber].category);
    setQuestion(data.results[questionNumber].question);
    setCorrectAnswer(data.results[questionNumber].correct_answer);

    const formattedData = data.results.map((category) => {
      const incorrectAnswersIndexes = category.incorrect_answers.length;
      const randomIndexes = Math.random() * (incorrectAnswersIndexes - 0) + 0;
      category.incorrect_answers.splice(
        randomIndexes,
        0,
        category.correct_answer
      );
      return {
        ...category,
        answers: category.incorrect_answers,
      };
    });

    setquizData(formattedData);
    setOptions(formattedData[questionNumber].answers);
  };

  useEffect(() => {
    fetchQuizData();
  }, []);
  const selectAnswer = (e, answer) => {
    setSelectedAnswer(answer);
    console.log("Answer Selected:", answer);

    const optionSelected = "options-selected";
    const newSelectedElement = e.target;

    if (newSelectedElement.classList.value === optionSelected) {
      newSelectedElement.classList.replace("options-selected", "options");
      setPrevSelectedElement(newSelectedElement);
    } else if (prevSelectedElement) {
      prevSelectedElement.classList.replace("options-selected", "options");
      newSelectedElement.classList.replace("options", "options-selected");
      setPrevSelectedElement(newSelectedElement);
    } else {
      newSelectedElement.classList.replace("options", "options-selected");
      setPrevSelectedElement(newSelectedElement);
    }
  };
  const checkAnswer = () => {
    console.log(
      selectedAnswer,
      correctAnswer,
      score,
      questionNumber,
      question,
      category,
      options
    );
    if (selectedAnswer === correctAnswer && questionNumber < 10) {
      setScore(score + 1);
      setQuestionNumber(questionNumber + 1);
      setQuestion(questionsData.results[questionNumber].question);
      setCategory(questionsData.results[questionNumber].category);
      setOptions(quizData[questionNumber].answers);
      setSelectedAnswer();
      setCorrectAnswer(questionsData.results[questionNumber].correct_answer);
      setShowAlert(false);

      console.log("Correct", questionNumber);
    } else if (
      selectedAnswer !== undefined &&
      selectedAnswer !== correctAnswer &&
      questionNumber < 10
    ) {
      setQuestionNumber(questionNumber + 1);
      setQuestion(questionsData.results[questionNumber].question);
      setCategory(questionsData.results[questionNumber].category);
      setOptions(quizData[questionNumber].answers);
      setSelectedAnswer();
      setCorrectAnswer(questionsData.results[questionNumber].correct_answer);
      setShowAlert(false);

      console.log("Incorrect", questionNumber);
    } else if (
      selectedAnswer !== undefined &&
      selectedAnswer === correctAnswer &&
      questionNumber === 10
    ) {
      console.log("Ready to start again?");
      setScore(score + 1);
      setQuizState("results");
      setShowAlert(false);
    } else if (
      selectedAnswer !== undefined &&
      selectedAnswer !== correctAnswer &&
      questionNumber === 10
    ) {
      console.log("Ready to start again?");
      setShowAlert(false);
      setQuizState("results");
    } else {
      setShowAlert(true);
    }
    console.log(
      selectedAnswer,
      correctAnswer,
      score,
      questionNumber,
      question,
      category
    );
  };
  console.log(selectedAnswer);
  return (
    <div>
      <Container>
        <div className="title-text">
          <h1 className="trivia-text">Trivia</h1>
          <h1 className="coin-text"> Coin</h1>
          <h1 className="game-text">Game</h1>
        </div>

        <Row>
          {quizState === "start" ? (
            <>
              <Card className="card-big">
                <Card.Title>
                  <div
                    className="category"
                    dangerouslySetInnerHTML={createMarkup(category)}
                  ></div>
                </Card.Title>
                <Card.Body>
                  <div className="score">
                    <p
                      dangerouslySetInnerHTML={createMarkup(score)}
                      className="correct-score"
                    ></p>
                    /
                    <p
                      className="total-question"
                      dangerouslySetInnerHTML={createMarkup(totalQuestion)}
                    ></p>
                  </div>
                  <div
                    className="question"
                    dangerouslySetInnerHTML={createMarkup(question)}
                  ></div>
                  {options.map((answer) => (
                    <div
                      onClick={(e) => selectAnswer(e, answer)}
                      key={answer}
                      value={answer}
                    >
                      <li
                        className="options"
                        dangerouslySetInnerHTML={createMarkup(answer)}
                      ></li>
                    </div>
                  ))}

                  {showAlert === true ? (
                    <>
                      <Alert
                        variant="light"
                        onClose={() => setShowAlert(false)}
                        dismissible
                      >
                        <Alert.Heading>Please choose an answer!</Alert.Heading>
                      </Alert>
                    </>
                  ) : (
                    <div className="alert-component"></div>
                  )}
                  {rightAlert === true ? (
                    <>
                      <Alert
                        variant="success"
                        onClose={() => setShowAlert(false)}
                        dismissible
                      >
                        <Alert.Heading>That's Right, Good Job!</Alert.Heading>
                      </Alert>
                    </>
                  ) : (
                    <div className="alert-component"></div>
                  )}
                  {wrongAlert === true ? (
                    <>
                      <Alert
                        variant="danger"
                        onClose={() => setShowAlert(false)}
                        dismissible
                      >
                        <Alert.Heading>
                          The Right Answer is <b>{correctAnswer}</b>. You Got
                          the Next One!
                        </Alert.Heading>
                      </Alert>
                    </>
                  ) : (
                    <div className="alert-component"></div>
                  )}
                  <Button
                    className="check-btn"
                    size="large"
                    variant="outline-light"
                    onClick={() => checkAnswer()}
                  >
                    CHECK ANSWER
                  </Button>
                </Card.Body>
              </Card>
            </>
          ) : (
            <Results
              createMarkup={createMarkup}
              score={score}
              totalQuestion={totalQuestion}
            />
          )}
        </Row>
      </Container>
    </div>
  );
};

export default QuizCategory;
1 Answers

Problem

When you do the following two lines you cannot expect that the questionNumber has incremented by one at the time of running the second line.

setQuestionNumber(questionNumber + 1);
setQuestion(questionsData.results[questionNumber].question);

Solution 1

You can use a useEffect to listen to question number changes and take action accordingly.

  useEffect(() => {
    if (questionNumber >= 1 && questionNumber < 10) {
      setQuestion(questionsData.results[questionNumber].question);
      setCategory(questionsData.results[questionNumber].category);
      setOptions(quizData[questionNumber].answers);
      setSelectedAnswer();
      setCorrectAnswer(questionsData.results[questionNumber].correct_answer);
      setShowAlert(false);
    }
  }, [questionNumber]);

Keep only the quesionNumber update logic in the if-else block whenever it updates useEffect will fire always with the incremented value.

Edit boring-cloud-boy8n5

Solution 2

You can just increment the questionNumber in the consecutive lines (questionNumber + 1 instead of just questionNumber) as below.

setQuestionNumber(questionNumber + 1);
setQuestion(questionsData.results[questionNumber + 1].question);
setCategory(questionsData.results[questionNumber + 1].category);
setOptions(quizData[questionNumber + 1].answers);
setSelectedAnswer();
setCorrectAnswer(questionsData.results[questionNumber + 1].correct_answer);
setShowAlert(false);

Edit friendly-firefly-n2qdog

You need to do this in both if and else if block

Related