Linked Questions

Popular Questions

I am working on creating a quiz page that fetches data using the useEffect hook. The data fetched include the question and the options which are multiple choice. I have a Check Answer button that displays the final score of the user when clicked. I also have a Play Again button that is displayed after the user's score has been rendered.

When the user clicks on the Play Again button, it is supposed to fetch another quiz data but this happens prematurely when the Check Answer button is pressed. What I mean is that, when the user clicks on the Check Answer button, it displays the score (good) but then the whole page gets re-rendered prematurely so the user can't see the options they got wrong and which were right. Clicking the Play Again button also re-renders the quiz page which was the intended behaviour. I have the playAgain state set as the dependency array of the useEffect so that it could re-render the quiz when the state changes. But I think that's what's responsible for the problem.

Another thing - dunno if I should ask it as a different question but I'll include it here - is that when the user clicks on the right option for each question, the options get reshuffled. It's probably not a big deal but I thought I needed to fix it to improve the UX. My guess is that it has something to do with the checkEachAnswer function but I'm at loss of what is wrong with it.

Any idea what could be wrong. Thanks.

Code for the Quiz component below.


export default function Quiz() {

  const [quiz, setQuiz] = useState(null);
  const [playAgain, setPlayAgain] = useState(false);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [userAnswer, setUserAnswer] = useState(() => []);
  const [showAnswer, setShowAnswer] = useState(false);
  const [showAnswerBtn, setShowAnswerBtn] = useState(true);
   

  // This hook fetches data once
  // Added error handling to prevent errors filling up the UI
  useEffect(() => {
    fetch("https://opentdb.com/api.php?amount=5&category=18&difficulty=hard&type=multiple")
      .then(result => {
        if (!result.ok) {
          throw new Error("This is an HTTP error", result.status);
        }
        else {
          return result.json();
        }
      })
      .then(data => {
          setQuiz(data.results);
          console.log("Quiz data stored", data);
          console.log(quiz);
          return quiz && console.log("This is quiz data", quiz);
      })
      .catch(error => {
        console.error("An error occurred!", error);
        setQuiz(null);
        setError(true);
      })
      .finally(() => {
        setLoading(false);
      });
    
  }, [playAgain])


  const quizElements = quiz && quiz.map(eachQuiz => {

    const incorrectOptions = eachQuiz.incorrect_answers;
    const correctOption = quiz && eachQuiz.correct_answer;
    let options = [];
    options = incorrectOptions.concat(correctOption);

    function createRandomOptions(arr) {
      
      let copyOptions = [...arr];
      let randomOptionsArr = [];

      while (copyOptions.length > 0) {
        let randomIndex = Math.floor(Math.random() * copyOptions.length);
        randomOptionsArr.push(copyOptions[randomIndex]);
        copyOptions.splice(randomIndex, 1); 
      }

      return randomOptionsArr;
    }

    let randomOptions = createRandomOptions(options);

    return (
      <>
        <div className="quiz-wrapper">
          <p className="question">{eachQuiz.question}</p>
          <ul>
            {quiz && randomOptions.map((option, index) => 
              {
                // Checks if the clicked option is the correct one and also checks if it was already picked before and prevents it from being added to the userAnswer array
                function checkEachAnswer() {
                  if (option === correctOption) {
                    console.log("Correct");

                    if (userAnswer.includes(option)) {
                      let userAnsArrCopy = [...userAnswer];
                      let index = userAnsArrCopy.findIndex(elem => elem);
                      userAnsArrCopy[index] = option;
                      
                      setUserAnswer(prevValue => {
                        return userAnsArrCopy;
                      }); 
                    }

                    else {
                      setUserAnswer(prevValue => {
                      return [...prevValue, option];
                      });
                    }

                  }
                  else {
                    console.log(option, "Is incorrect", );
                  }
                }
          
                return (
                  <li 
                    key={option} 
                    className="option"
                    onClick={() => checkEachAnswer()}
                  >
                    {option}
                  </li>
                )
              })
            }
          </ul>
        </div>
      </>
    )
  });

  console.log(userAnswer);

  function displayAnswer() {
    setShowAnswer(true);
    setPlayAgain(true);
    setShowAnswerBtn(false);
  }

  function updatePlayAgain() {
    setPlayAgain(false);
    setShowAnswer(false);
    setShowAnswerBtn(true);
    setUserAnswer([]);
  }

  return (
    <>
      {loading && <h3>Currently loading...</h3>}
      {error && <h3>An error occurred while fetching data!</h3>}
      {quiz && <h1 className="topic">Topic: Computer Science</h1>}
      
      {quiz && quizElements}

      {showAnswer && <p>You scored {userAnswer.length} / {quiz.length}</p>}

      {quiz && showAnswerBtn && 
        <button 
          onClick={() => displayAnswer()}
          className="main-btn"
        >
          Check Answer
        </button>
      }

      {quiz && playAgain && 
        <button 
          onClick={() => updatePlayAgain()}
          className="main-btn"
        >
          Play Again
        </button>
      }
    </>
  )
}


I would also include for the App component incase the problem isn't my Quiz component.

import { useState } from "react";
import Quiz from "./components/Quiz";

const quizId = [
  {"id": 0},
  {"id": 1},
  {"id": 2},
  {"id": 3},
  {"id": 4},
]

export default function App() {
  
  const [startQuiz, setStartQuiz] = useState(false);

  function loadQuiz() {
    console.log(startQuiz);
    setStartQuiz(true);
  }


  return (
    <>
      {!startQuiz && 
        <div className="intro-container">
        
          <div className="intro-screen">
            <h1>Quiz My Heart</h1>
            <button className="start-btn" onClick={loadQuiz}>
              Go to Quiz
            </button>
          </div>
        
        </div>}

      {startQuiz && 
        <main> 
          <Quiz key={quizId.map(myId => myId.id)}/>
        </main>
      }
    </>
  );
}

Related Questions