How to target single item in list with onClick when mapping in ReactJS

I have a data file with an array of objects and each object consists of “Question” and “Multi choice answers” . What I’m doing is mapping over the array and displaying the list of questions, which works just fine. and I want the background of the option to change when I click on it but it is changing [EVERY element] ,in the list instead of just that one item that was clicked on. and i also wanted it to change to green if the answer is correct and to red of the answer is wrong.

below is the Array of data

xport const QuestionsList = [
  {
    image: BrazilFlag,
    options: [
      { Answertext: "Brazil", IsitCorrect: true },
      { Answertext: "Germany", IsitCorrect: false },
      { Answertext: "Oman", IsitCorrect: false },
      { Answertext: "Russia", IsitCorrect: false },
    ],
  },
  {
    image: IndiaFlag,
    options: [
      { Answertext: "USA", IsitCorrect: false },
      { Answertext: "Saudi Arabia", IsitCorrect: false },
      { Answertext: "India", IsitCorrect: true },
      { Answertext: "Sirilanka", IsitCorrect: false },
    ],
  },

and below is my React Component

export const Question1 = () => {
  const [currentQuestion, setCurrentQuestion] = useState(0);
  const [showScore, setShowScore] = useState(true);
  const [score, setScore] = useState(0);

  const AnsweredQuestion = (IsitCorrect) => {
    const nextQuestion = currentQuestion + 1;

    if (IsitCorrect) {
      setScore(score + 1);
    }
    if (nextQuestion < QuestionsList.length) {
      setCurrentQuestion(nextQuestion);
    } else {
      setShowScore(true);
    }
  };

  return (
    <motion.div
      className="question-box"
      variants={Question1Variant}
      initial="hidden"
      animate="visible"
      exit="exit"
    >
      <div className="show-score">
        {showScore ? (
          <div className="score-section">
            {`You scored ${score} out of ${QuestionsList.length}`}
          </div>
        ) : (
          <>// ... quiz question/answer markup</>
        )}
      </div>
      <motion.div className="question-flag">
        <img src={QuestionsList[currentQuestion].image} alt="photo" />
      </motion.div>

      <div className="question-heading">
        <h1>Pick The Right Country's Flag</h1>
      </div>
      <div className="question-option">
        {QuestionsList[currentQuestion].options.map((option, index) => {
          return (
            <button
              key={index}
              className="question-option-paragraph "
              onClick={() => AnsweredQuestion(option.IsitCorrect)}
            >
              {option.Answertext}
            </button>
          );
        })}
      </div>

your feedback is pretty much appreciated.

You want to change the button’s color, but in AnsweredQuestion it immediately moves to the next question. So you might want to add setTimeout or a ‘Next Question’ button. You also need a state of whether the question has answered or not yet.

This code works if you want to show the correct answer after attempting the question

export default function QuizComponent() {
  const [currentQuestion, setCurrentQuestion] = useState(0)
  const [showScore, setShowScore] = useState(false);
  const [haveAnswered, setHaveAnswered] = useState(false);
  const [score, setScore] = useState(0);

  const QuestionList = [{
    image: "https://img.icons8.com/ios-glyphs/120/000000/image.png",
    options: [
      { Answertext: "Brazil", IsitCorrect: true },
      { Answertext: "Germany", IsitCorrect: false },
      { Answertext: "Oman", IsitCorrect: false },
      { Answertext: "Russia", IsitCorrect: false },
    ],
  },
  {
    image: "https://img.icons8.com/ios-glyphs/120/000000/image.png",
    options: [
      { Answertext: "USA", IsitCorrect: false },
      { Answertext: "Saudi Arabia", IsitCorrect: false },
      { Answertext: "India", IsitCorrect: true },
      { Answertext: "Sirilanka", IsitCorrect: false },
    ],
  }]

  const AnsweredQuestion = (IsitCorrect) =>{
    setHaveAnswered(true)
    const nextQuestion = currentQuestion + 1;

    if (IsitCorrect) {
      setScore(score + 1)
    }
    setTimeout(()=>{
      if (nextQuestion < QuestionList.length) {
        setCurrentQuestion(nextQuestion)
      } else {
        setShowScore(true)
      }
      setHaveAnswered(false)
    }, 1000)
  }

  return (
    <div>
      <div id="show-score">
        {showScore ?
          <div className="score-section">
            {`You scored ${score} out of ${QuestionList.length}`}
          </div>

          : <></>
        }
      </div>

      <div>
        <img src={QuestionList[currentQuestion].image} />
      </div>

      <div id="question-heading">
        <h1>Pick The Right Country's Flag</h1>
      </div>

      <div id="question-option">
        {QuestionList[currentQuestion].options.map((option, index) => {
          return (
            <button
              key={index}
              className={`question-option-paragraph text-white font-bold py-2 px-4 rounded ${haveAnswered? (option.IsitCorrect?`bg-green-500 hover:bg-green-700`:`bg-red-500 hover:bg-red-700`):`bg-blue-500 hover:bg-blue-700`}`}
              onClick={() => AnsweredQuestion(option.IsitCorrect)}
            >
              {option.Answertext}
            </button>
          );
        })}
      </div>


    </div>
  )
}

But it changes all button’s color, if you want to change only the selected button you need an extra identifier because key is not accessible in react.

noChild

One approach is to refactor each button as a child component and pass props individually

export default function QuizComponent() {
  const [currentQuestion, setCurrentQuestion] = useState(0)
  const [showScore, setShowScore] = useState(false);
  const [haveAnswered, setHaveAnswered] = useState(false);
  const [score, setScore] = useState(0);

  const QuestionList = [{
    image: "https://img.icons8.com/ios-glyphs/120/000000/image.png",
    options: [
      { Answertext: "Brazil", IsitCorrect: true },
      { Answertext: "Germany", IsitCorrect: false },
      { Answertext: "Oman", IsitCorrect: false },
      { Answertext: "Russia", IsitCorrect: false },
    ],
  },
  {
    image: "https://img.icons8.com/ios-glyphs/120/000000/image.png",
    options: [
      { Answertext: "USA", IsitCorrect: false },
      { Answertext: "Saudi Arabia", IsitCorrect: false },
      { Answertext: "India", IsitCorrect: true },
      { Answertext: "Sirilanka", IsitCorrect: false },
    ],
  }]

  const AnsweredQuestion = (IsitCorrect) => {
    setHaveAnswered(true)
    const nextQuestion = currentQuestion + 1;

    if (IsitCorrect) {
      setScore(score + 1)
    }
    setTimeout(() => {
      if (nextQuestion < QuestionList.length) {
        setCurrentQuestion(nextQuestion)
      } else {
        setShowScore(true)
      }
      setHaveAnswered(false)
    }, 1000)
  }

  return (
    <div>
      <div id="show-score">
        {showScore ?
          <div className="score-section">
            {`You scored ${score} out of ${QuestionList.length}`}
          </div>

          : <></>
        }
      </div>

      <div>
        <img src={QuestionList[currentQuestion].image} />
      </div>

      <div id="question-heading">
        <h1>Pick The Right Country's Flag</h1>
      </div>

      <div id="question-option">
        {QuestionList[currentQuestion].options.map((option, index) => {
          return (
            <QuestionOption
              key={index}
              parentFunction={AnsweredQuestion}
              correctness={option.IsitCorrect}
              defaultClass="question-option-paragraph text-white font-bold py-2 px-4 rounded"
              colors={{
                default: "blue",
                good: "green",
                bad: "red"
              }}
              haveAnswered={haveAnswered}
              text={option.Answertext}
            >
            </QuestionOption>
          );
        })}
      </div>


    </div>
  )
}

function QuestionOption(props) {
  const [myColor, setMyColor] = useState(props.colors.default)

  const changeColor = () => {
    if (props.correctness) {
      setMyColor(props.colors.good)
    }
    else {
      setMyColor(props.colors.bad)
    }
  }

  useEffect(() => {
    if (!props.haveAnswered) {
      setMyColor(props.colors.default)
    }
  }, [props.haveAnswered])

  return (
    <button
      className={`${props.defaultClass} bg-${myColor}-500 hover:bg-${myColor}-700`}
      onClick={() => {
        props.parentFunction(props.correctness)
        changeColor()
      }}
    >
      {props.text}
    </button>
  )
}

Now you get the desired result.

withChildComponent

2 Likes

Hello, Good Day Please I tried this out but it is not getting this exact output. Any chance you could please help me out?