1

I'm confused how scope works in React.

I want to pass a value of a state to another state but what I'm finding is that in one function it prints the right value and in another it does not.

It's a countdown where a timer counts down and counts clicks, and at the very end, I want to get the total amount of clicks and send it to a state of total. So the states are:

  const [strokeScore, setStrokeScore] = useState(1);

  const [totalStrokeScore, setTotalStrokeScore] = useState(1);

  const [strokeCountdown, setStrokeCountdown] = useState();

With strokeScore being the clicks of the game - and I want to get that total and shoot it over to total stroke score.

For some reason I can't do that - what is going on here?

import React, { useEffect, useState } from 'react';

function ScoreCard() {

  const [strokeScore, setStrokeScore] = useState(1);

  const [totalStrokeScore, setTotalStrokeScore] = useState(1);

  const [strokeCountdown, setStrokeCountdown] = useState();

  const strokeCountdownDing = new Audio('/sounds/round-complete.mp3');

  const endOfGameRound = () => {
    strokeCountdownDing.play();
    document.getElementById('stroke-counter-button').disabled = true;
  }

  const addToStrokeScore = () => {
    setStrokeScore(strokeScore + 1);
    // prints the correct number
    console.log(strokeScore)
    if (strokeCountdown === 0) {
        endOfGameRound()
    }
  }

  const subtractStrokeScore = () => {
    setStrokeScore(strokeScore - 1);
  }

  const countDown = () => {
    // let strokeCountdown = Math.floor(Math.random() * 31) + 100;
    let strokeCountdown = 10
    let strokeCountdownSpeedOptions = [1000, 500, 300, 200];
    let strokeCountDownSpeed = strokeCountdownSpeedOptions[Math.floor(Math.random()*strokeCountdownSpeedOptions.length)];
    let strokeCounter = setInterval(() => {
        strokeCountdown--
        // this is always giving me...1...?
        console.log('the score is: ' + strokeScore)
        setStrokeCountdown(strokeCountdown)
        if (strokeCountdown === 0) {
            endOfGameRound()
            clearInterval(strokeCounter)
            setTotalStrokeScore(strokeScore);
        }
    }, strokeCountDownSpeed)
  }

  useEffect(() => {
    countDown();
  }, []);

  return (
    <div className="gane__score-card">
      <div className="gane__speed-level">
          Speed: idk
      </div>
      <div className="gane__stroke-countdown">
          Countdown: {strokeCountdown}
      </div>
      <p>Score: {strokeScore}</p>
      <button id="stroke-counter-button" onClick={addToStrokeScore}>
          {strokeCountdown === 0 ? 'Game Over' : 'Stroke'}
      </button>
      {/* window.location just temp for now */}
      {strokeCountdown === 0 
        ? <button onClick={() => window.location.reload(false)}>Play Again</button>
        : <button disabled>Game in Progress</button>
      }
      <div className="gane__total-score">
          Total score: {totalStrokeScore}
      </div>
    </div>
  );
}

export default ScoreCard;

If you look at the sample above, I comment where it gives the correct number and where it seems to be stuck at "1" - why is this?

I tried as a different variable too.

import React, { useEffect, useState } from 'react';

function ScoreCard() {

  const [strokeScore, setStrokeScore] = useState(1);

  const [totalStrokeScore, setTotalStrokeScore] = useState(1);

  const [strokeCountdown, setStrokeCountdown] = useState();

  const strokeCountdownDing = new Audio('/sounds/round-complete.mp3');

  // make new variable, maybe? 
  let strokeScoreCount = 0;

  const endOfGameRound = () => {
    strokeCountdownDing.play();
    document.getElementById('stroke-counter-button').disabled = true;
  }

  const addToStrokeScore = () => {
    setStrokeScore(strokeScore + 1);
    // prints the correct number
    console.log(strokeScore)
    // but this just gives me zero?
    strokeScoreCount = strokeScoreCount + 1;
    console.log('maybe do a different var? : ' + strokeScoreCount)
    if (strokeCountdown === 0) {
        endOfGameRound()
    }
  }

  const subtractStrokeScore = () => {
    setStrokeScore(strokeScore - 1);
  }

  const countDown = () => {
    // let strokeCountdown = Math.floor(Math.random() * 31) + 100;
    let strokeCountdown = 10
    let strokeCountdownSpeedOptions = [1000, 500, 300, 200];
    let strokeCountDownSpeed = strokeCountdownSpeedOptions[Math.floor(Math.random()*strokeCountdownSpeedOptions.length)];
    let strokeCounter = setInterval(() => {
        strokeCountdown--
        // this is always giving me...1...?
        console.log('the score is: ' + strokeScore)
        // this just gives me zero all the time? why? 
        console.log('the new variable to use is: ' + strokeScoreCount)
        setStrokeCountdown(strokeCountdown)
        if (strokeCountdown === 0) {
            endOfGameRound()
            clearInterval(strokeCounter)
            setTotalStrokeScore(strokeScore);
        }
    }, strokeCountDownSpeed)
  }

  useEffect(() => {
    countDown();
  }, []);

  return (
    <div className="game__score-card">
      <div className="game__speed-level">
          Speed: idk
      </div>
      <div className="game__stroke-countdown">
          Countdown: {strokeCountdown}
      </div>
      <p>Score: {strokeScore}</p>
      <button id="stroke-counter-button" onClick={addToStrokeScore}>
          {strokeCountdown === 0 ? 'Game Over' : 'Stroke'}
      </button>
      {/* window.location just temp for now */}
      {strokeCountdown === 0 
        ? <button onClick={() => window.location.reload(false)}>Play Again</button>
        : <button disabled>Game in Progress</button>
      }
      <div className="game__total-score">
          Total score: {totalStrokeScore}
      </div>
    </div>
  );
}

export default ScoreCard;

Using a new variable just gives me zero all the time, and strokeScore is just permanently stuck at one in that function. But it's a state, can't you get state anywhere?

How do take value from one state, ensure it's correct in all scopes, and pass it to another state?

Note: the answer below explains state and scope but code breaks the setInterval. That question is here: useState with an argument in it's array is breaking a setInterval and makes it glitchy and erratic

halfer
  • 19,824
  • 17
  • 99
  • 186
kawnah
  • 3,204
  • 8
  • 53
  • 103
  • Does this answer your question? [Why is setState in reactjs Async instead of Sync?](https://stackoverflow.com/questions/36085726/why-is-setstate-in-reactjs-async-instead-of-sync) – Someone Special Feb 06 '22 at 18:07
  • sort of...my understanding of state is when a component is active you can access its state value at whatever state its currently in. since it's "async" are we saying that as the component is rendered the application in its async state is always evaluating that to 1? if thats the case how do I change my code to accomodate this? is there another concept I should be using thats not state? – kawnah Feb 06 '22 at 18:09
  • If you do `setState(somevalue); console.log(state)`, the `state` will show the old value instead of the new one because it is yet updated. – Someone Special Feb 06 '22 at 18:11
  • that makes sense but `const countDown` is always stuck at 1, shouldn't it get the new value? – kawnah Feb 06 '22 at 18:12
  • and isn;t `Countdown: {strokeCountdown}` representative of the updated state? – kawnah Feb 06 '22 at 18:12
  • I have added explanation in a answer. – Someone Special Feb 06 '22 at 18:13

1 Answers1

1

If you do setState(somevalue); console.log(state), the state will show the old value instead of the new one because it is yet updated.

if you do the following, strokeScore will reflect old value because it's yet updated.

    setStrokeScore(strokeScore + 1);
    console.log(strokeScore)

You can see the updated score in a useEffect call. Try to do the following then you know what i am talking about.

  useEffect(() => {
          console.log('Score in useeffect', strokeScore);
  }, [strokeScore])

  const addToStrokeScore = () => {
         setStrokeScore(strokeScore + 1);
         console.log('Score in function', strokeScore);
  }

In addition, if your new value is dependent on old value, the correct way to set state is

         setStrokeScore( prev => prev + 1);

So the final codes should be similar to the following

 useEffect(() => {
          console.log('Score in useeffect', strokeScore); //shows new value
  }, [strokeScore])

  const addToStrokeScore = () => {
         setStrokeScore(prev => prev + 1);
         console.log('Score in function', strokeScore); //shows old value
  }

Or even better, to take things one set step up.

  const addToStrokeScore = () => {
         const newScore = strokeScore + 1;
         // do whatever you need with the new score, then update state when done.
         setStrokeScore(newScore);
         console.log('Score in function', newScore);
   }
Someone Special
  • 12,479
  • 7
  • 45
  • 76
  • this is a helpful explaination but now that code with the strokeScore argument in that array breaks the timer and it acts erratic....I'll open a new question and mark this answered for now. – kawnah Feb 06 '22 at 18:23