skip to Main Content

I want to call a function at regular intervals and update state inside it.

I started with a useEffect but eventually ended up here. I tried using Ref.current to store the interval and clear it every time in the function and recreate it but it still wasn’t working. Something like this

const drawBall = () => {
  clearInterval(intervalRef.current);
  // Access state and update it
  intervalRef.current = setInterval(drawBall, 1000);
}

I dropped the idea and thought of using a simple setTimeout. Everytime drawBall is called, it calls a new Timeout, even if the closure takes the value at the time of being called, it still will have the updated value as the value at the time of calling is updated. But it is not working as expected, the function still gets the old value even though the value is being updated correctly. The code goes like this –

const drawBall = () => {
 console.log(ballPos); // Shows initial value in every call (not the expected behaviour)
 setBallPos(prevVal => {
   console.log(prevVal); // Shows the right value
   return {
       x: prevVal.x + 2,
       y: prevVal.y + 2
   }
 });

 setTimeout(drawBall, 1000);
}

What am I missing here?

Edit:
Switching to use-effect doesn’t help. It updates ballPos correctly, but that was happening before too. The issue still remains that when I access ballPos.x even inside the same useEffect, it shows the original value, not the updated one.

This is the new useEffect code

 useEffect(() => {
        const interval = setInterval(() => {

            const canvas = gameBoard.current;
            const ctx = canvas.getContext('2d');
            let currX = ballPos.x;
            let currY = ballPos.y;
            currX += defaultDX;
            currY += defaultDY;
            console.log(currX, currY); // Shows Old Value
            ctx.fillStyle = "#FF0000";
            ctx.beginPath();
            ctx.arc(currX, currY, 10, 0, Math.PI * 2, true);
            ctx.fill();

            setBallPos((prevVal) => {
                return {
                    x: prevVal.x + defaultDX,
                    y: prevVal.y + defaultDY
                }
            });
        }, 1000);

        return () => {
            clearInterval(interval);
        }
    }, [])

2

Answers


  1. To resolve the issue and to continuously update the state at regular intervals, you should consider using the useEffect hook along with setInterval to update the state.

    const [ballPos, setBallPos] = useState({ x: 0, y: 0 });
    useEffect(() => {
      const interval = setInterval(() => {
        setBallPos((prevPos) => ({
          x: prevPos.x + 2,
          y: prevPos.y + 2,
        }));
      }, 1000);
    
      return () => {
        clearInterval(interval); // clean up the interval on component unmount
      };
    }, []);
    
    Login or Signup to reply.
  2. While you’re updating the state correctly, you continue to use the old value to draw on the canvas because the ballPos reference inside the interval callback is not aware of state changes.

    You could use the new state value for the draw call as shown below, or move the requestAnimationFrame part to its own Effect entirely.

    useEffect(() => {
        const interval = setInterval(() => {
            setBallPos((prevVal) => {
                const newBallPos = {
                    x: prevVal.x + defaultDX,
                    y: prevVal.y + defaultDY
                };
                requestAnimationFrame(() => {
                    const canvas = gameBoard.current;
                    const ctx = canvas.getContext('2d');
                    console.log(newBallPos);
                    ctx.fillStyle = "#FF0000";
                    ctx.beginPath();
                    ctx.arc(newBallPos.x, newBallPos.y, 10, 0, Math.PI * 2, true);
                    ctx.fill();
                });
                return newBallPos;
            });
        }, 1000);
        return () => clearInterval(interval)
    }, [])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search