0

The quiz generally works ok, but when I click 2 times in the same answer, I count the points 2 times, like I would click 100 times in the same correct answer I have 100 points. I don't know how to fix it .. Please help ...

QuestionBox:

const QuestionBox = ({ question, options, selected }) => {
    const [answer, setAnswer] = useState(options);
    return (
        <div className="questionBox">
            <div className="question">{question}</div>
            {(answer || []).map((text, index) => (
                <button key={index} className="answerBtn" onClick={() => {
                    setAnswer([text]);
                    selected(text)
                }}>{text}</button>
            ))}
        </div>
    )
}

computeAnswer:

computeAnswer = (answer, correctAnswer) => {
        if (answer === correctAnswer) {
            this.setState({
                score: this.state.score + 1,
            })

render:

{this.state.qBank.map(
                            ({ question, answers, correct, id }) => (
                                <QuestionBox key={id} question={question} options={answers} selected={Answers => this.computeAnswer(Answers, correct)} />

                            )
                        )}
drcielak
  • 81
  • 1
  • 8

2 Answers2

2

You can have a boolean flag in QuestionBox state, initialized to false, and switched to true on the first click, then bypass score calculation if this flag is true :

const QuestionBox = ({ question, options, selected }) => {
    const [answer, setAnswer] = useState(options);
    const [alreadyAnswered, setAlreadyAnswered] = useState(false);

    return (
        <div className="questionBox">
            <div className="question">{question}</div>
            {(answer || []).map((text, index) => (
                <button disabled={alreadyAnswered} key={index} className="answerBtn" onClick={() => {
                    if(!alreadyAnswered) {
                        setAnswer([text]);
                        selected(text);
                        setAlreadyAnswered(true);
                    }
                }}>{text}</button>
            ))}
        </div>
    )
}

I also add disabled attribute if question has already been answered to let user knows that this is one-time only.

Also it would be a good idea to put the onClick logic in a function to improve performance (https://medium.com/@Charles_Stover/cache-your-react-event-listeners-to-improve-performance-14f635a62e15).

By the way you should avoid to init state with props : React component initialize state from props

Florian Motteau
  • 3,467
  • 1
  • 22
  • 42
  • *avoid to init state with props* If you're referring to the top answer there, the problem looks to be setting state *that never changes* from props, but here, the state *is* changing via `setAnswer`, right? Or were you referring to something else? – CertainPerformance May 18 '20 at 22:39
  • indeed in this case it should be OK, but I thought it was a good idea to note the fact that in general it is a bad idea, and it should not become a habit or something. – Florian Motteau May 18 '20 at 22:43
  • If it's a bad idea to put initial props into initial state, then what's the alternative? Do you mean that the parent component should handle the state for all of its children components instead? – CertainPerformance May 18 '20 at 22:46
  • The usual pattern should be to have a parent component passing values and setters to children, and to use props in children to display data, and use the setter as event handler to modify the props value, as described here : https://reactjs.org/tutorial/tutorial.html#lifting-state-up. In some case though this is not applicable, but here it is a valid approach IMHO – Florian Motteau May 18 '20 at 22:51
  • Thanks, that makes sense. Appreciate it! – CertainPerformance May 18 '20 at 23:15
1

Currently, answer will either be an array of possible answer strings, or an array containing the single answer chosen by the user. You could change it so that the click listener is only attached if the length of the answer is greater than 1:

<button key={index} className="answerBtn" onClick={answer.length === 1 ? null : () => {
    // rest of the code

This way, once the user chooses an answer, further clicks on the (now single rendered) button won't do anything, and the score will only be (possibly) incremented the first time the button is clicked.

answer is a bit of a strange variable name for a collection of answer strings - perhaps rename it to answerOptions or possibleAnswers or something like that, for better readability.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320