skip to Main Content

In a function inside a functional react component I may update multiple state variables. How can I ensure all state changes have taken place before running the next function.

Below, for example, imagine that calculateScore() is triggered on some event (perhaps some clicks in a game) and adds to the value of either a, b or both. I then wan’t to run determineWinner(). How can I run determineWinner() in a way which ensures both setA and setB have completed their asynchronous runs so that both a and b are up-to-date (after a single calculateScore() run)?

I didn’t find this in the docs or while searching around for a while here on stackOverflow.

Example

function MyComponent() {
    let [a, setA] = useState(0);
    let [b, setB] = useState(0);
    let [winnerText, setWinnerText] = useState("")

    function determineWinner() {
        if (a > b) {
            // Note, I don't want this to happen if the score ends up equal after both setA and setB from calculateScore() completes.
            setWinnerText("A won!")
        }
    }

    function calculateScore(aShouldIncrease, bShouldIncrease) {
        if (aShouldIncrease) setA(prevA => prevA + 1);
        if (bShouldIncrease) setB(prevB => prevB + 1);

        determineWinner(); // Doesn't work to run it here, since setA and setB are asynchronous.
    }

    useEffect(() => {
        determineWinner(); // Not guaranteed to work here, since perhaps only setA has completed so far and not setB? Right?
    }, [a, b])

    determineWinner(); // Even if we imagined I had some other condition to prevent it from being run prematurely, it may run when only a or only b has changed, right?

    return <h1>{ winnerText }</h1>
}

I realize I could solve it by making all the calculations within the calculateScore without setting state, but I wonder if there is some point in react where I can be sure all setState() functions started from within a single function call will be completed.

2

Answers


  1. function calculateScore(aShouldIncrease, bShouldIncrease) {
       unstable_batchedUpdates(() => {
         if (aShouldIncrease) setA(prevA => prevA + 1);
         if (bShouldIncrease) setB(prevB => prevB + 1);
    
          determineWinner();
       });
    }
    
    Login or Signup to reply.
  2. Semantically it sounds like you want to perform an action when "the game score" is updated, not when any individual value therein is updated. So there’s a level of encapsulation not accounted for in the current code.

    Keep the "game state" in one state object rather than two. For example, consider this:

    const [scores, setScores] = useState({a: 0, b: 0});
    

    Then updating that state would be a single "set" operation:

    function calculateScore(aShouldIncrease, bShouldIncrease) {
        const newScores = { ...scores };
        if (aShouldIncrease) {
            newScores.a++;
        }
        if (bShouldIncrease) {
            newScores.b++;
        }
        if (aShouldIncrease || bShouldIncrease) {
            setScores(newScores);
        }
    }
    

    Then since only one state object was updated, you can trigger the effect with just that dependency:

    useEffect(() => {
        determineWinner();
    }, [scores]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search