javascriptreactjsreact-hooksstategame-loop

React state updated properly except inside gameloop function


I have this very simple react code that declares a state variable called money using the useState hook.

Then I trigger a gameloop to start once using the useEffect hook.

Inside this gameloop, I simply increment the value of the money state variable.

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

export default function Game() {
  // declare one state variable
  const [money, setMoney] = useState(100);

  // start gameloop
  useEffect(() => {
    let anim = requestAnimationFrame(gameloop);

    return () => {
      cancelAnimationFrame(anim);
    };
  }, []);

  function gameloop() {
    setMoney(money => money + 1);
    console.log(money); // always returns 100
    requestAnimationFrame(gameloop);
  }

  return (
    <div>
      <div>{money}</div>
    </div>
  );
}

The UI properly gets updated, so does the state when I look at it with the react dev tools.

However, inside the gameloop function, when I do a console.log(money); it always prints 100.

It seems that if I try to read the money variable inside my gameloop function, it is always the initial states and never the real one.

Any ideas of what am I doing wrong here ?


Solution

  • The gameloop function was declared when money had the value 100, in the initial render. It is this same function, in whose scope the money variable is still 100, that gets called on each frame, rather than the function that gets created on each render (which has the "freshest" value in money).

    To sync the two schedules (rAF vs. React), you can use the useRef hook. For example, recreate the gameloop on each render:

    let gameloop = useRef();
    gameloop.current = function() {
      ...
      requestAnimationFrame(gameloop.current);
    }
    

    In effect, the useRef hook creates a sort of instance property on a function component.