tangway / flag-guessing-game-frontend

0 stars 0 forks source link

animation sequence for emoji button does not render properly when user has exhausted all 4 attempts #43

Open tangway opened 5 months ago

tangway commented 5 months ago

the animation sequence renders properly when correct === true but for some reason the first part of the sequence is skipped when numberOfAttempts === 4 even though they're in the same if statement

in the case where numberOfAttempts === 4 and at the 4th attempt the user gets the answer correct the 2 step animation sequence is rendered properly

useEffect(() => {
    if (numberOfAttempts === 4 || correct === true) {
      setGameHasEnded(true);

      // first part of animation
      controls.start({
        scale: 1,
        transition: { duration: 2.5, ease: 'easeInOut', delay: 5 },
      });

      // second part of animation
      // the timeout value has to be longer than the duration+delay in the first
      // part of animation otherwise the second part will override the first part
      setTimeout(() => {
        controls.start({
          // scale: [1, 0.9, 1.1, 1], // Shake scale values
          scale: [1, 0.8, 1],
          transition: {
            duration: 2.5,
            repeat: 'Infinity',
            repeatType: 'reverse',
            ease: 'easeInOut',
          },
        });
      }, 7600);
tangway commented 5 months ago

i separated the 2 conditions into 2 useEffects to help debug the situation, also added a .then() promise to check that the first part of the animation runs

useEffect(() => {
    if (numberOfAttempts === 4) {
      // setGameHasEnded(true);

      // first part of animation
      controls.start({
        scale: 1,
        // x: [0, 0],
        // y: [0, 0],
        transition: { duration: 2.5, ease: 'easeInOut', delay: 5 },
      }).then(() => {
        console.log("First part of animation complete.")});

      // second part of animation
      // the timeout value has to be longer than the duration+delay in the first
      // part of animation otherwise the second part will override the first part
      setTimeout(() => {
        controls.start({
          // scale: [1, 0.9, 1.1, 1], // shake scale values
          scale: [1, 0.8, 1],
          transition: {
            duration: 2.5,
            repeat: 'Infinity',
            repeatType: 'reverse',
            ease: 'easeInOut',
          },
        });
      }, 7600);
    }
  }, [numberOfAttempts]);

  useEffect(() => {
    if (correct === true) {
      // setGameHasEnded(true);

      // first part of animation
      controls.start({
        scale: 1,
        // x: [0, 0],
        // y: [0, 0],
        transition: { duration: 2.5, ease: 'easeInOut', delay: 5 },
      }).then(() => {
        console.log("First part of animation runs.")});

      // second part of animation
      // the timeout value has to be longer than the duration+delay in the first
      // part of animation otherwise the second part will override the first part
      setTimeout(() => {
        controls.start({
          // scale: [1, 0.9, 1.1, 1], // shake scale values
          scale: [1, 0.8, 1],
          transition: {
            duration: 2.5,
            repeat: 'Infinity',
            repeatType: 'reverse',
            ease: 'easeInOut',
          },
        });
      }, 7600);
    }
  }, [correct]);
tangway commented 5 months ago

added some other console log statements to track state of numberOfAttempts and correct the problem still persists, and console statements shows that in both cases the first part of the animation actually runs so we can be sure of that now

looking at the debug statements the only difference between when user reaches numberOfAttempts === 4 and when user gets correct answer correct === true is that the correct state changes to true.

one of the possible reasons the 1st part animation doesnt render is that it is overwritten by a re-render of the component as one of its state changes when it is being executed but i have yet to find out which state that is

using React Devtools could help to track the different state changes in each user flow

tangway commented 5 months ago

in 4 attempts player failed flow, this is the sequence of state changes as shown in React Devtools Profiler:

  1. numberOfAttempts: 4; buttonClicked: ["Belize", "Dominica", "Peru", "Tunisia"]
  2. gameHasEnded: true
  3. startFlagAnimation: true; playerFailed: true

in 4 attempts, where player gets correct answer in the 4th attempt, this is the sequence of state changes that allowed the 2 step animation sequence to run correctly:

  1. correct: true; startFlagAnimation: true; score: 1; numberOfAttempts: 4; buttonClicked: ["Belize", "Dominica", "Peru", "St Lucia"]; correctButton: "St Lucia"; gameHasEnded: true
  2. playerFailed: true

it could be that in the first flow that extra step in state change somehow interrupted the rendering of 1st part of the animation. the second flow updates all but 1 state in the first step and consists of only 2 steps

tangway commented 5 months ago

have reduced the first flow to only 2 steps in state change by combining 3 state changes into 1 useEffect but the problem stays the same

useEffect(() => {
    console.log('useEffect numberOfAttempts:', numberOfAttempts);
    if (numberOfAttempts === 4 && correct === false) {
      setGameHasEnded(true);
      setStartFlagAnimation(true);
      setPlayerFailed(true);
    }
  }, [numberOfAttempts, correct]);

so with the above change, for 4 attempts player failed flow this is the sequence of state changes as shown in React Devtools Profiler:

  1. numberOfAttempts: 4; buttonClicked: ["Belize", "Dominica", "Peru", "Tunisia"]
  2. gameHasEnded: true; startFlagAnimation: true; playerFailed: true
tangway commented 4 months ago

continued working on the possibility that it's the extra number of steps in state change that caused the 1st part of the animation to be cancelled out. what if i could bundle all the state change into checkAnswer function like what a correct answer flow does?

so i took out the above useEffect and placed what it does into a callback function in setNumberOfAttempts:

const checkAnswer = event => {
    const target = event.currentTarget;

    if (!gameHasEnded) {
      target.disabled = true;
      setButtonClicked(buttonClicked.concat(target.id));
      // setNumberOfAttempts(prevNumber => prevNumber + 1);
      setNumberOfAttempts(prevNumber => {
        const newNumber = prevNumber + 1;
        if (newNumber === 4 && correct === false) {
          setGameHasEnded(true);
          setStartFlagAnimation(true);
          setPlayerFailed(true);
        }
        return newNumber;
      });
      console.log('numberOfAttempts in !gameHasEnded: ', numberOfAttempts);
    }

    if (target.textContent === correctAnswer) {
      // when answer is correct
      setCorrect(true);
      setGameHasEnded(true);
      setCorrectButton(target.id);
      setScore(score + 1);
      setStartFlagAnimation(true);
    } else {
      // when answer is wrong
      setCorrect(false);
    }
  };

so now with 4 attempts failed this is the state change:

  1. numberOfAttempts: 4; buttonClicked: ["Belize", "Dominica", "Peru", "Tunisia"]; gameHasEnded: true; startFlagAnimation: true; playerFailed: true

all in 1 step and this might totally explain why it worked!!!