react: State variable not updating in useEffect callback?
Do you want to request a feature or report a bug? Bug, maybe? Although thinking about it more makes me think I’ve misunderstood something.
What is the current behavior?
scroll state variable updates in rendered output but not inside handleScroll event callback.
I reckon this might be due to the fact that when handleScroll is defined scroll is 0, but scroll is defined in the scope above and should be updated when the component is re-rendered.
import React, { useState, useEffect } from "react";
const Scroller = () => {
const [scroll, setScroll] = useState(window.scrollY);
const handleScroll = () => {
console.log(scroll); // scroll is always 0
setScroll(window.scrollY);
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []); // runs once
return <div id="scroll">{scroll}</div>; // scroll is correct
};
export default Scroller;
What is the expected behavior?
scroll to be updated inside handleScroll event callback and in render output.
Which versions of React, and which browser / OS are affected by this issue?
chrome 70.0.3538.77
react 16.7.0-alpha.0 - next
react-dom 16.7.0-alpha.0 - next
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 56
- Comments: 27
Commits related to this issue
- workaround https://github.com/facebook/react/issues/14066 — committed to simonmysun/indoor-sound-classification-frontend by simonmysun a year ago
@evolutionxbox
It is not a bug, but the normal behavior from the doc. You can check this issue for more detail https://github.com/facebook/react/issues/14042
In short, if you are using the second parameter
[]then you need to put all the variable (state or not) that you rely on.So in your case, if you want to have access to the
scrollstate variable then you need to put[scroll]😃Obviously you maybe don’t want to do that, because you don’t want to create / remove event listener everytimes.
So the other way is to use an other effect, that will be responsible to modifying or getting the value.
useReduceris one way. (see the other issue for code example)I hope it is clear 😃
Yep that’s right. We might offer a more convenient way in the future.
The rule of thumb is that if you use a variable in
useEffect, you must declare it in theuseEffectdependency array (or omit the array entirely). So in your examplehandleScrollshould be in the array.It’s true that this would cause it to re-subscribe more often. There is a way to avoid it but it’s not very convenient and might cause other issues in the future. We plan to add a better way later but it will take some time to implement.
if you need a real unmount, not the crapy fake one that you find in all the examples and a one that has the correct state … add this to your hook function 😄 100% fix 😄
In you hook function:
@gpietro It seems like it. Current behavior just feels broken and the “workarounds” to achieve something as simple as this are just not worth it.
We’ve submitted a proposal to solve this. Would appreciate your input!
https://github.com/reactjs/rfcs/pull/220
I had the exact same problem in my branch. I solved it similar to this, but I don’t think it is nice at all. Looking forward to the next update.
@TidyIQ
I think you want to use useState if you want it to be consistent. Something like:
I’m having the same problem so I won’t open a new issue for it. Hopefully someone here can help me out with it.
Here’s the code:
And the console output on initial render of the whole app:
Then when I open the component that uses the labelWidth value the entire console becomes:
useEffect isn’t updating the labelWidth property after mount. I’ve followed the instructions above so I’m not sure what the issue is. Any help?
I filed https://github.com/facebook/react/issues/14099 to track the root problem.
To anyone coming to this conversation late (like me). I ended up using
useRefas suggested above and making a little hook to autosave form data on unmount:It can then be used in my form component:
The key is to
useRefand update theref.currentwith a useEffect every time the values are updated. This gives you a current representation of the state when the cleanup callback is fired.In case if you see stale or older values of state or props in useEffect callback or in event handlers, React suggests you to use refs keep the update values. This behavior is not due to React but its due to what values of a variable a javascript function receive at run-time (lexical scope and closures). React devs have tried to address this issue here: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function
My opinion, the approach in the above doc solves the issue but seems a bit of a life hack. There are more quirks like this with hooks, so I still would prefer a class-based components for writing components with complex state and business logic.
@gaearon any comments?
almost same problem.
I am updating the global state using context api, but when I am calling global state in
useEffect()it still retreive the initial value, not updated value. here I used [], as second parameter for useEffect function. when I remove it, it execute all code withing useEffect function as unlimited.Swapping
setIntervalforsetTimeoutdid the job. I’m also using the “clean up” function in theuseEffectto remove the timeout when the component updates.See live demo: https://codesandbox.io/s/brave-kalam-j7xkb
This did help. Thanks @AyWa and @gaearon