0

I am trying to write a quiz type module where you can select one of two answers.


const [active, setActive] = useState(true);


const toggle = () => {
  setSelected(!selected)
}


            <div id="quizAnswers">
              <div className="answer {active ? 'active' : ''}">
             {quiz.answers[0].text}
                <button label="{active ? 'ACTIVE' : 'inactive'}" onClick={toggle}>
                {active ? "ACTIVE" : "inactive"}
                </button>
              </div>

              <div className="answer {active ? 'active' : ''}">
              {quiz.answers[1].text}
                <button label="{active ? 'ACTIVE' : 'inactive'}" onClick={toggle}>
                {active ? 'ACTIVE' : 'inactive'}
                </button>
              </div>

This is about as far as I can figure out how to get. I am trying to get it so that when I click one button, the label and text switch to active, while switching the other to inactive, if it is currently active.

I've seen some stuff with react-DOM but I'm wondering if there is a more straight forward way to accomplish this? Thanks in advance.

2 Answers2

1

You can just have the state be the index of the active button, like Nicholas said in the comments:


// the default active button is the first button, button number 0
const [activeButton, setActiveButton] = useState(0);


const toggle = () => {
  setSelected(!selected)
}

return(

            <div id="quizAnswers">
              <div className={`answer ${activeButton === 0 ? 'active' : ''}`}>
             {quiz.answers[0].text}
                <button label={`${activeButton === 0 ? 'ACTIVE' : 'inactive'}`} onClick={() => setActiveButton(0)}>
                {active ? "ACTIVE" : "inactive"}
                </button>
              </div>

              <div className={`answer ${activeButton === 1 ? 'active' : ''}`}>
              {quiz.answers[1].text}
                <button label={activeButton === 1 ? 'ACTIVE' : 'inactive'} onClick={() => setActiveButton(1)}>
                {active ? 'ACTIVE' : 'inactive'}
                </button>
              </div>
  )

And you can reduce duplication and make it generic to more than 2 buttons by using map:

return(
  <div id="quizAnswers">
   {quiz.answers.map((answer, index) => 
     <div className={`answer ${activeButton === index ? 'active' : ''}`}>
       {answer.text}
       <button label={activeButton === index ? 'ACTIVE' : 'inactive'} onClick={() => setActiveButton(index)}>
         {active ? "ACTIVE" : "inactive"}
       </button>
     </div>
   }
  </div>
)

Also, note how I changed the syntax in className and label-- you were embedding {} within double quotes (like " foo {bar} baz") which wouldn't work like you were expecting.

Nathan
  • 73,987
  • 14
  • 40
  • 69
  • I believe i've tried that syntax before and it didnt work either but I will give it another try. thank you! edit- it worked! I must have missed something when i tried interpolating it before. thank you!! – Brittany Hilliard Sep 19 '22 at 22:44
  • No problem! It's definitely a bit confusing because brackets (`{}`) are functioning in two different ways in this syntax. The outer brackets are JSX syntax, that allows for embedding javascript within JSX. Inside of them, you have backticks which create a "template literal" string in javascript, and within that you use `${..}` for string interpolation to embed javascript *within the string* – Nathan Sep 19 '22 at 22:51
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals – Nathan Sep 19 '22 at 22:51
  • So yeah, two different kinds of escaping happening there, and definitely looks ugly/confusing on face value – Nathan Sep 19 '22 at 22:52
0

Just as an additional example: you can hold a reference to the actual answer object inside of your useState variable. Also, it might be better to create a new component for actual Answer and use it inside the .map function. Or without map function, but why to duplicate code?

const { useState, Fragment } = React;

const DUMMY_QUIZ = {
  question: "How to do this?",
  answers: [
    { id: 1, text: "Do that" },
    { id: 2, text: "Do this" }
  ]
};

function QuizAnswers() {
  const [selectedAnswer, setSelectedAnswer] = useState();

  const quiz = DUMMY_QUIZ;

  return (
    <div id="quizAnswers">
      <p>Using map function</p>
      {quiz.answers.map((a) => (
        <QuizAnswer
          item={a}
          key={a.id}
          onClick={() => setSelectedAnswer(a)}
          isSelected={a === selectedAnswer}
        />
      ))}

      <p>Without map function</p>
      <Fragment>
        <QuizAnswer
          item={quiz.answers[0]}
          onClick={() => setSelectedAnswer(quiz.answers[0])}
          isSelected={quiz.answers[0] === selectedAnswer}
        />
        <QuizAnswer
          item={quiz.answers[1]}
          onClick={() => setSelectedAnswer(quiz.answers[1])}
          isSelected={quiz.answers[1] === selectedAnswer}
        />
      </Fragment>
    </div>
  );
}

function QuizAnswer({ item, onClick, isSelected }) {
  return (
    <div className={`answer ${isSelected ? "active" : ""}`}>
      {item.text}
      <button label={`${isSelected ? "ACTIVE" : "inactive"}`} onClick={onClick}>
        {isSelected ? "ACTIVE" : "inactive"}
      </button>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<QuizAnswers />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
Sergey Sosunov
  • 4,124
  • 2
  • 11
  • 15
  • 1
    I personally wouldn't recommend that approach, as it stores more info than needed in the state, and having to consider object equality checks is something I like to avoid. I like keeping my state minimal generally, and using the index (or id) works fine in this instance and is the common pattern for selecting one or many elements in an array. Btw I like how you embedded a react app in the answer! – Nathan Sep 19 '22 at 22:49
  • honestly guys, the index part of this had nothing to do with the code at hand, that was just referencing the text of the answer being received from the api. I was having a problem with tracking the state until the number part was mentioned, and then had the syntax slighty wrong which was further confusing it. But it is all functioning as expected now. I will definitely keep all of this information in mind for future projects! – Brittany Hilliard Sep 19 '22 at 22:59