-
Notifications
You must be signed in to change notification settings - Fork 45.9k
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
Comments
This is subtle but expected behavior. When
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 // 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);
}; |
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. |
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);
}
},[]); |
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 ) |
another way does not use useRef is that you define a variable outside the react function component, and at the beginning of the function component, you need to update that variable to match the lastest state value, that is, no need to use useRef if you find it easier by this way, happy coding! |
Not sure if that's a good strategy, but in a similar case we used the following idea: import { useImperativeHandle, useRef, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [timeoutCount, setTimeoutCount] = useState(null);
const ref = useRef(null);
useImperativeHandle(ref, () => ({
onTimeout: () => setTimeoutCount(count),
}));
const getCountTimeout = () => {
setTimeout(() => ref?.current?.onTimeout(), 2000);
};
return (
<div className="App">
Count: <button onClick={() => setCount(count + 1)}>{count}</button>
<br />
<br />
<button onClick={getCountTimeout}>Get count with timeout</button>
<br />
<p>Count from timeout: {timeoutCount}</p>
</div>
);
} In our case that's more practical than using a ref to the state as suggested before, mainly because our state is quite large. |
same issue here |
Would this be bad practice as a workaround for this issue and avoid creating a ref?
This works and when the timer code is executed, both counters have the same value (5). Basically, we make sure to access the current value of counter and not the value at the time the timer is scheduled by accessing it inside setCounter and setting the state of the timerCounter inside it as well, and then just making sure to return the currentValue in setCounter so "counter" is not affected or even causing a new render. |
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
The text was updated successfully, but these errors were encountered: