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 ?
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.