简体   繁体   中英

How to optimize the quiz buttons in React.js

I am working on a building a quiz app. I have already built it. but so far I am not happy with my code. I believe the code is not scalable and concise enough. I was wondering if you could help me make my code better.

what I need is, when the user clicks one of the options, if it is correct, then make the button clicked into green background. otherwise, red and correct buttons into green. for now, it is working as it should. but lets say down the road, I have to have questions that contains more than 2 options like 3, 5, or even more. then, this code will not work. and the logic gets even uglier.

Could you please help me out of it?

const questions = [
  {
    id: 1,
    question: "which one of them is fruit?",
    options: ["apple", "rice"],
    answers: ["correct", "wrong"]
  },
]

function App() {

  const [btnA, setBtnA] = React.useState("gray")
  const [btnB, setBtnB] = React.useState("gray")

  const handleButtonA = (e, answer) => {
    e.preventDefault()
    if(answer == "correct") {
      setBtnA("green");
    }
    if(answer === "wrong") {
      setBtnA("red")
      setBtnB("green")
    }
  }

  const handleButtonB = (e, answer) => {
    e.preventDefault();
    if(answer === "correct") {
      setBtnB("green")
    }
    if(answer === "wrong") {
      setBtnA("green")
      setBtnB("red")
    }
  }

  return (
    <div className="App">
      {questions.map(q => {
        return (
          <div key={q.id}>
            <h3>{q.question}</h3>
            <button style={{background: btnA}} onClick={e => handleButtonA(e, q.answers[0])}>{q.options[0]}</button>
            <button style={{background: btnB}} onClick={e => handleButtonB(e, q.answers[1])}>{q.options[1]}</button>
          </div>
        )
      })}
    </div>
  );
}

First of all, your code won't work with multiple questions. Since you share the same state for all buttons, clicking the right/wrong button will update all of them.

2nd of all, I'd recommend you change from this:

options: ["apple", "rice"],
answers: ["correct", "wrong"]

to:

options: [
    "apple",
    "rice",
],
correct: 0

Where correct is the index is of the correct option. This way you don't have to check for a string, and in general, it is more oriented.

Now, we dont need to know the colors in the state, we just need to know if the question has been attempted or not. For that, we can create a state which is an array.

const [quizData, setQuizData] = useState(questions.map(question => { 
    return {
        attempted: -1
    }
}))

Now finally, we can loop over each element in the array.

return (
<div className="App">
  {questions.map((q, questionIndex) => {
    const currData = quizData[questionIndex];
    
    // Method to handle when button is clicked
    // valid is weather the question clicked was valid
    const handleClick = (index) => {
    
        // we use a method which recieves the currentState when using setState
        // in order to prevent react scheduling issues
        setQuizData((prevData) => {
            // for the current question, set attempted to true
            prevData[questionIndex].attempted = index;
            
            // destructure the array because react doesnt work well with returning the same object instance
            return [
                ...prevData
            ]
        })
    }

    return (
      <div key={q.id}>
        <h3>{q.question}</h3>
        {/* Looop over each option */}
        {question.options.map((option, index) => {
        
            // take gray as the default color
            let color = "gray";
            
            // if the user has attempted already, check to change the color
            if (currData.attempted >= 0) {
                if (index == currData.attempted) {
                    if (index == q.correct) {
                        color = "green";
                    }
                    else {
                        color = "red"
                    }
                }
                else if (index == q.correct) {
                    color = "greeen"
                }
            }
            
            // over here the button's enabled property is something I added, but you can remove that if you'd like
            return (
                <button
                enabled={!currData.attempted}
                key={index}
                style={{background: color}}
                onClick={() => handleClick(index)}>
                    {option}
                </button>
            )
        }}
      </div>
    )
  })}
</div>
);

So in the end, our code is:

const questions = [
  {
    id: 1,
    question: "which one of them is fruit?",
    options: [
        "apple",
        "rice",
    ],
    correct: 0
  },
]

function App() {

    const [quizData, setQuizData] = useState(questions.map(question => { 
        return {
            attempted: -1
        }
    }))
    

    return (
    <div className="App">
      {questions.map((q, questionIndex) => {
        const currData = quizData[questionIndex];

        // Method to handle when button is clicked
        // valid is weather the question clicked was valid
        const handleClick = (index) => {

            // we use a method which recieves the currentState when using setState
            // in order to prevent react scheduling issues
            setQuizData((prevData) => {
                // for the current question, set attempted to true
                prevData[questionIndex].attempted = index;

                // destructure the array because react doesnt work well with returning the same object instance
                return [
                    ...prevData
                ]
            })
        }

        return (
          <div key={q.id}>
            <h3>{q.question}</h3>
            {/* Looop over each option */}
            {question.options.map((option, index) => {

                // take gray as the default color
                let color = "gray";

                // if the user has attempted already, check to change the color
                if (currData.attempted >= 0) {
                    if (index == currData.attempted) {
                        if (index == q.correct) {
                            color = "green";
                        }
                        else {
                            color = "red"
                        }
                    }
                    else if (index == q.correct) {
                        color = "greeen"
                    }
                }

                // over here the button's enabled property is something I added, but you can remove that if you'd like
                return (
                    <button
                    enabled={!currData.attempted}
                    key={index}
                    style={{background: color}}
                    onClick={() => handleClick(index)}>
                        {option}
                    </button>
                )
            }}
          </div>
        )
      })}
    </div>
    );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM