notmynamelastname
notmynamelastname

Reputation: 105

Clicking reverts array order in react

I'm making a Trivia App in React. I shuffle the answers before rendering them as buttons and want to change the color of the buttons if the answer is right/wrong:

export default function QuestionCard({ question }){
    const [isAnswered, setIsAnswered] = useState(false)
    
    const initAnswers = [...question.incorrect_answers, question.correct_answer]
    
    function shuffleAnswers(answers){
        var j, x, i; 
        for(i = answers.length - 1; i > 0; i--){
            j = Math.floor(Math.random()* (i + 1));
            x = answers[i];
            answers[i] = answers[j];
            answers[j] = x;
        }
        return answers
    }

    const answers = shuffleAnswers(initAnswers);

    const handleAnswerClick = (answer) => {
        setIsAnswered(true)
    }

    return(
        <Card>
            {question.question}
            <div>
                {answers.map(answer => (
                    <Button 
                        key={answer}
                        onClick={() => handleAnswerClick(answer)}
                        color={isAnswered && answer === question.correct_answer ?
                             'primary' : 
                             isAnswered && answer !== question.correct_answer ? 'secondary' : 
                            'default'}
                    >
                        {answer}
                    </Button>
                ))}
            </div>
        </Card>
    )
}

When after clicking the Button, the Card 'forgets' the order from shuffleAnswers() and returns the Buttons in their original order. Any

Upvotes: 0

Views: 38

Answers (1)

Drew Reese
Drew Reese

Reputation: 202801

Issue

On each render cycle initAnswers and answers are both redeclared/redefined, thus when state is updated when a card is clicked on and state updates the component rerenders with new initAnswers and shuffled answers arrays.

Solution

You want to only initialize and shuffle the answers once per set of question answers. You could use an useEffect hook to shuffle and store in state the answers array, but IMO just memoizing the answers array is better.

  1. Use useMemo hook with answers dependencies (i.e. only recompute when the incorrect and correct answers for question prop update.
  2. Move the shuffleAnswers utility function out of the component, it doesn't have any state/prop/component dependencies and we don't want to redefine it each render cycle either.
  3. Move initAnswers into the hook callback so it isn't an external dependency to computing answers (otherwise you would need to memoize it as well!).

Code:

function shuffleAnswers(answers) {
  let j, x, i;
  for (i = answers.length - 1; i > 0; i--) {
    j = Math.floor(Math.random() * (i + 1));
    x = answers[i];
    answers[i] = answers[j];
    answers[j] = x;
  }
  return answers;
}

function QuestionCard({ question }) {
  const [isAnswered, setIsAnswered] = useState(false);

  const answers = useMemo(() => {
    const initAnswers = [
      ...question.incorrect_answers,
      question.correct_answer
    ];

    return shuffleAnswers(initAnswers);
  }, [question.incorrect_answers, question.correct_answer]);

  ...

  return (
    ...
  );
}

Edit clicking-reverts-array-order-in-react

Fun Fact

You can use array destructuring assignment to swap two values in an array in a single line without a third variable

[answers[j], answers[i]] = [answers[i], answers[j]]

So your utility can reduce to

function shuffleAnswers(answers) {
  let i, j;
  for (i = answers.length - 1; i > 0; i--) {
    j = Math.floor(Math.random() * (i + 1));
    [answers[j], answers[i]] = [answers[i], answers[j]];
  }
  return answers;
}

Upvotes: 1

Related Questions