Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update React State variable inside an Interval

I create this simple example script to animate a player position using a simple setInterval function but impossible to make it works..

Everytime it go into the interval function the playerPos is back to is initial value..

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

const App = () => {

  let [playerPos, setPlayerPos] = useState({x:128, y:128})

  useEffect(() => {

    setInterval(() => {

      console.log(`Current player pos ${JSON.stringify(playerPos)}`);
      setPlayerPos({x:128, y:playerPos.y + 10});

    }, 1000);

  }, []);

  return <div style={{position: "absolute", top: `${playerPos.x}px`, left: `${playerPos.y}px`}}></div>
}

Strangely, my console only showing:

Current player pos {"x":128,"y":128}
Current player pos {"x":128,"y":128}
Current player pos {"x":128,"y":128}
...

What did I misunderstood in hooks and effects ??!

like image 648
Arthur Avatar asked Oct 25 '25 01:10

Arthur


1 Answers

The useEffect only ran once, when the component initially loaded, and at the time only had the initial value of the playerPos state. So you effectively have a closure around that one value that's just repeating.

Instead of using setInterval, rely on useEffect to run multiple times whenever the state changes by adding that state value to its dependency array. Then just use setTimeout to add the delay each time. For example:

useEffect(() => {
  setTimeout(() => {
    console.log(`Current player pos ${JSON.stringify(playerPos)}`);
    setPlayerPos({x:128, y:playerPos.y + 10});
  }, 1000);
}, [playerPos]);

So each time the component renders, useEffect will re-run if playerPos has changed. And each time it runs, playerPos will be changed one second later, triggering a re-render.


Note that this would always leave an active timeout when the component unloads. So as an additional step you'll want to provide useEffect with a way to cancel the timeout. How one does this is by returning a "cleanup" function form the useEffect operation, which would release any resources held by the logic within the operation.

In this case it's just a simple call to clearTimeout:

useEffect(() => {
  const timeout = setTimeout(() => {
    console.log(`Current player pos ${JSON.stringify(playerPos)}`);
    setPlayerPos({x:128, y:playerPos.y + 10});
  }, 1000);
  return () => clearTimeout(timeout);
}, [playerPos]);
like image 116
David Avatar answered Oct 26 '25 23:10

David



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!