Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State from useState hook inside a setTimeout is not updated #14010

Closed
Ikos9 opened this issue Oct 28, 2018 · 4 comments

Comments

Projects
None yet
5 participants
@Ikos9
Copy link

commented Oct 28, 2018

Do you want to request a feature or report a bug?
Bug/Question

What is the current behavior?
When I retrieve a value from a useState hook inside a setTimeout function, the value is the one when the function was called and not when the code inside gets executed.

You can try here, just increase the counter then start the timeout and increase the counter again before the timeout expires.
https://codesandbox.io/s/2190jjw6op

What is the expected behavior?
Retrieving the updated state.
If instead it's working as intended how I can retrieve the updated status?

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

16.7.0-alpha.0

@aweary

This comment has been minimized.

Copy link
Member

commented Oct 29, 2018

This is subtle but expected behavior. When setTimeout is scheduled it's using the value of count at the time it was scheduled. It's relying on a closure to access count asynchronously. When the component re-renders a new closure is created but that doesn't change the value that was initially closed over.

If instead it's working as intended how I can retrieve the updated status?

In this case you need to use a "container" that you can write the updated state value to, and read it later on in the timeout. This is one use case for useRef. You can sync the state value with the ref's current property and read current in the timeout instead.

// Use a ref to access the current count value in
// an async callback.
const countRef = useRef(count);
countRef.current = count;

const getCountTimeout = () => {
  setTimeout(() => {
    setTimeoutCount(countRef.current);
  }, 2000);
};

https://codesandbox.io/s/6zo5y2p8qk

@aweary aweary closed this Oct 29, 2018

@gaearon

This comment has been minimized.

Copy link
Member

commented Oct 29, 2018

I'd note that in many cases this is the desired behavior. For example if you subscribe to an ID, and later want to unsubscribe, it would be a bug if ID could change over time. Closing over the props/state solves those categories of issues.

@strobox

This comment has been minimized.

Copy link

commented Mar 2, 2019

And if you need modify state itself in timeout/interval you should not use state from closure, instead get it like that:

  const [count, setCount] = React.useState(0);
  React.useEffect( () => {
    const i_id = setInterval(() => {
      setCount(currCount => currCount+1)
    },3000);
    return () => {
      clearInterval(i_id);
    }
  },[]);
@terry90

This comment has been minimized.

Copy link

commented Mar 21, 2019

As @strobox mentioned, providing a callback when using the function returned from useState is really efficient for this. I'm using it with a setTimeout to hide notifications after an animation:

  const [notifications, setNotifications] = useState<Notification[]>([]);

  useEffect(() => {
    const listener = setMessageHook(MessageType.Error, (resp: ErrorBody) => {
      const uid = getUid();
      setNotifications((notifs) =>
        addNotification(notifs, { id: uid, value: resp.error }),
      );
      // ----- Interesting part
      setTimeout(
        () => setNotifications((notifs) => removeNotification(notifs, uid)),
        animationDuration * 1000,
      );
      // -----------------------
    });

    return function cleanup() {
      chrome.runtime.onMessage.removeListener(listener);
    };
  }, []);

I hope it will help future people with this issue. (Thanks @strobox )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.