0

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 ?

Palmi
  • 3
  • 2
  • `money` = 100 because is `const` and outside template keeps initial value that is not updated. Did you try access it via `this.money`? – Daniel Jun 18 '20 at 18:50
  • When I try this.money, I get `TypeError: Cannot read property 'money' of undefined`. I think you are on the right track, the gameloop function seems to only have access to the initial value. Should I remove the const ? – Palmi Jun 18 '20 at 18:54

2 Answers2

0

You are doing nothing wrong. This is because the React state is updated asynchronously. This mean when you want to console log the state of the money variable, the state is not updated yet. What you can do is log it from the useEffect function, something like this:

useEffect(() => {
    let anim = requestAnimationFrame(gameloop);
    console.log(money);    
    return () => {
      cancelAnimationFrame(anim);
    };
  }, [money]);
Paul Miranda
  • 738
  • 17
  • 39
  • Thanks for the answer, I appreciate it! But after a few seconds, my money variable would be way greater than 100 and the UI properly gets updated. Same for my state inside the dev tools. It feels like the gameloop function only ever has access to the initial state and that's it. – Palmi Jun 18 '20 at 18:46
  • @Palmi I added a way in you can console log it every time it changes – Paul Miranda Jun 18 '20 at 18:49
0

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.

Dan
  • 9,912
  • 18
  • 49
  • 70
  • Is there a way of giving my gameloop function access to the most recent state ? Or calling a "fresh" gameloop function in my requestAnimationFrame ? – Palmi Jun 18 '20 at 19:00
  • I've added some pointers, hopefully it helps, I'm on my phone so I can't add a more elaborate answer currently :-) – Dan Jun 18 '20 at 19:14