Search code examples
javascriptreactjsreact-hooksconditional-operator

Toggle correct answer message with radio button in React


Given a small quiz app, I'm trying to toggle a message under the questions to show the user that they have selected the correct answer or incorrect answer.

I have a small message that I'm gating with a boolean value which is set by the selection of the radio button (though the correct radio button isn't getting selected on initial click for some reason, only incorrect answers are), and need it to show a message of "You got it right" if the answer is true or "incorrect" if false. I can show true, but the logic isn't working to show false if the answer is incorrect and then clear the messaging when the "next question" button is clicked.

function App() {
  let [points, setPoints] = useState(null);
  let [counter, setCounter] = useState(null);
  let [question, setQuestions] = useState();
  let [isCorrect, setIsCorrect] = useState(false); <==Store correct answer selected
  
  function Answer(props) {
    return (
      <li aria-labelledby="answers-list">
        <label>
          <input
            type="radio"
            name="answer_group"
            className="answer"
            value={props.answer}
            onChange={checkAnswer}
          />
          {props.answer}
        </label>
      </li>
    );
  }

  function checkAnswer(e) {
    let val = e.target.value;
    let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
    displayCorrect(ans.correct);
    //Toggles message to true if answer is correct
    ans.correct === true ? setIsCorrect(true) : setIsCorrect(false);
  }

  function Quiz(props) {
    return (
      <div className="quiz">
        <div className="quesiton" role="h2">
          {props.question}
        </div>
        <ul className="answers">{props.children}</ul>
      </div>
    );
  }

  function displayCorrect(correct) {
    let correct_msg = correct ? "correct" : "incorrect";
    console.log("Answer was " + correct_msg);
  }

  function nextQuestion() {
    setIsCorrect(false); <== Should hide message when user selects "next question" button
    if (document.querySelector('input[name="answer_group"]:checked') == null) {
      alert("Must select an answer before proceeding to the next question");
      return;
    }
    let val = document.querySelector('input[name="answer_group"]:checked')
      .value;
    let answerObj = question[counter].answers.filter(
      (ans) => ans.value === val
    )[0];
    let updated_points = answerObj.correct ? points + 1 : points;
    setPoints(updated_points);
    let nextQuestion = counter + 1;
    if (counter < question.length - 1) {
      setCounter(nextQuestion);
    } else {
      setCounter(0);
    }
    displayCorrect(answerObj.correct);
  }
  
  return (
    <div className="Quiz slide-top">
      {!is_started ? (
        <div className="start-intro-wrapper">
          <h1 className="name">Quiz App</h1>
          <Starter start={start} />
        </div>
      ) : (
        <div className="quick-wrapper slide-in-bottom">
          <Quiz question={question[counter].question}>
            {question[counter].answers.map((answer, index, arr) => {
              return (
                <Answer
                  key={index}
                  index={index}
                  answer={answer.value}
                  correct={answer.correct}
                />
              );
            })}
          </Quiz>
          <div className="answer-controls">
            {isCorrect === true ? <p>You got it right!</p> : null} <== Messaging should show if user has selected correct message if correct option is selected
            <button onClick={(e) => nextQuestion()}>
              Next question
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Codesandbox Link


Solution

  • It doesn't display anything because you put null in the other condition for the display.

    If you replace it with {isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>} the correct message is displayed.

    You can also remove completly the function displayCorrect it's only writing in the console.

    If you want to clear the message when the user click on "next question" you could do it with a new variable hasAnswered set at false by default. You set it to true in your checkAnswer function, and to false in your nextQuestion function.

    The code for the text at the bottom would then looks like:

    {hasAnswered && 
        (isCorrect === true ? <p>You got it right!</p> : <p>You got it wrong!</p>)
    }
    
    

    Regarding the issue with you needing to click twice to update the radio button selected, i think it's a re-render issue when isCorrect change.

    To avoid the issue, you could store the value checked by the user and use this to display if the radio should be checked or no. It works with the following changes

      let [isChecked, setChecked] = useState(null);
    
    [...]
    
      function Answer(props) {
        return (
          <li aria-labelledby="answers-list">
            <label>
              <input
                checked={isChecked===props.answer}
                type="radio"
                name="answer_group"
                className="answer"
                value={props.answer}
                onChange={checkAnswer}
              />
              {props.answer}
            </label>
          </li>
        );
      }
    
    
      function checkAnswer(e) {
        let val = e.target.value;
        let ans = question[counter].answers.filter((ans) => ans.value === val)[0];
        setIsCorrect(ans.correct);
        setChecked(ans.value);
      }
    
    

    You also need setChecked(null) in your nextQuestion function to reset everything.