skip to Main Content
const TestApp = () => {
  const [speed, setSpeed] = useState(1);

  // a state updated in oanother function, but below function can not get new state
  const incrementSpeed = () => {
    console.log(speed, `speed`); // Always console 1, how can I get new state?
    window.requestAnimationFrame(incrementSpeed);
  };

  useEffect(() => {
    const handle = (e) => {
      if (e.key === 'o') {
        setSpeed(() => speed + 1);
      } else if (e.key === 'p') {
        incrementSpeed();
      }
    };
    window.addEventListener('keydown', handle);
    return () => window.removeEventListener('keydown', handle);
  }, [speed]);
  return <></>;
};

export default TestApp;

Guys, I got a problem, I have updated my code.

4

Answers


  1. If I’m not mistaken, what you’re looking for is a way to properly increment the value. Here’s how I would go about it.

    React state updates especially on functions with hooks are going to be batched, so whenever you console.log in your code, you will see the current value of variable speed. It is expected as it hasn’t run through the full cycle of re-render/state update.

    To ensure that increment is done properly based on what the current value is, you can pass a function into the setter function that does exactly that.

    Your incrementSpeed function would look like below:

    const incrementSpeed = () => {
      console.log(speed, `speed`);
      setSpeed(currentSpeed => currentSpeed + 1); // Note that we passed the function here, as it can also accept this kind of function which accepts the current state as argument, we could use it immediately like so.
      window.requestAnimationFrame(incrementSpeed);
    };
    

    You may also log it inside that function like below, but I’m not sure why you would want that in the current context but here it is:

    setSpeed(currentSpeed => {
      console.log('previous speed', currentSpeed);
     return currentSpeed + 1
    });
    
    Login or Signup to reply.
  2. speed is a closed in variable. Think closures. The value of speed was closed over by incrementSpeed. It is logging and using the same value.

    See, the incrementSpeed which is created afresh with the new state value is not the one passed into requestAnimationFrame. requestAnimationFrame is still using only one version of incrementSpeed which has the value of speed as 1.

    With hooks based React, the best place to see updated state values is inside the render body itself. There you can see the updated values.

    So move console.log at the same level as incrementSpeed.

    But the problem that the speed reference is closed over still exists. So you will have to use the updater function pattern of setState. This guarantees that you get the previous state value correctly and is the recommended way.

    const App = () => {
      const [speed, setSpeed] = useState(1);
    
      const incrementSpeed = () => {
        setSpeed(speed=> speed + 1);
        window.requestAnimationFrame(incrementSpeed);
      };
    
      useEffect(() => {
        window.addEventListener('keydown', (e) => {
          if (e.key === 'z') {
            incrementSpeed();
          }
        });
      }, []);
      console.log(speed, `speed`); 
    
      return <></>;
    };
    

    Demo

    NOTE: While console.log inside the setter can be done to check the value, it is not recommended as it is a side effect.

    Login or Signup to reply.
  3. In React Functional components when you update the state you can’t get the latest values of State in function. when you call the function next time you will get the last updated value.

    For example:

    keydown 1st time:
    log: 1 speed
    speed value of state after update: 2

    keydown 2nd time:
    log: 2 speed
    speed value of state after update: 3

    To get the latest value in console log:

    const TestApp = () => {
        const [speed, setSpeed] = useState(1);
    
        const incrementSpeed = () => {
          s = speed +1
          console.log(s, `speed`); // Always console 1, how can I get new state?
          setSpeed(s);
          window.requestAnimationFrame(incrementSpeed);
        };
    
        useEffect(() => {
          window.addEventListener('keydown', (e) => {
            if (e.key === 'z') {
              incrementSpeed();
            }
          });
        }, []);
        return <></>;
      };
    
      export default TestApp;
    

    Or like this:

    const incrementSpeed = () => {
        // Use a functional update to ensure you're always working with the latest state
        setSpeed(currentSpeed => {
          const newSpeed = currentSpeed + 1;
          console.log(newSpeed, `speed`); // This will log the updated speed
          return newSpeed;
        });
        window.requestAnimationFrame(incrementSpeed);
      };
    
    Login or Signup to reply.
  4. As per this answer here https://stackoverflow.com/a/67244182/11976596

    State variables don’t update itself inside an EventListener. If you want to update and access some state inside an EventListener, look into useRef. Also, you need to replace your 2nd useState statement with useEffect since that’s what you wanted to implement.

    A better approach would be to pass updated state as parameter as below

     const [speed, setSpeed] = useState(1);
    
      const incrementSpeed = (speed) => {
        console.log(speed, `speed`); // Always console 1, how can I get new state?
        setSpeed(speed + 1);
        window.requestAnimationFrame(incrementSpeed);
      };
    
      const keydownEvent = (e) => {
        if (e.key === 'z') {
          incrementSpeed(speed);
        }
      }
    
      useEffect(() => {
        window.addEventListener('keydown',keydownEvent);
        return () => {
          window.removeEventListener('keydown',keydownEvent);
        }
      }, []);
      return <>{speed}</>;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search