For some reason, when I acces ‘height’ or ‘scrollPos’ in the ‘handleScroll’ function, they have their initial value. However, if I add a ‘p’ tag with ‘{height}’, it is displayed correctly.
const [scrollPos, setScrollPos] = useState(0);
const [height, setHeight] = useState(0);
const [top, setTop] = useState(0);
const ref = useRef(null);
useEffect(() => {
setHeight(ref?.current?.clientHeight);
setScrollPos(-window.pageYOffset);
window.addEventListener("scroll", handleScroll);
}, []);
const handleScroll = () => {
const x = -window.pageYOffset;
const dif = x - scrollPos;
setTop(top + dif > 0 ? 0 : top + dif < -height ? -height : top + dif);
setScrollPos(x);
};
I am converting the component from a CC to a RFC, and it worked before.
2
Answers
You can use a callback function as the second argument of the useState hook, which will receive the previous state value as a parameter, and return the new state value based on it. This way, you can avoid using stale state values in your event handler. For example:
The problem comes from the fact that the state of
handleScroll
never changes when it’s triggered by the event. So the callback of the event needs to be recreated every time it is needed to have the current state updated inside the implementaiton of the callback.Here’s a solution:
In the dependency array of the
useEffect
creating the event, pass the state variables to be used. And, to avoid the event to be duplicated every time theuseEffect
callback is called, it needs to be removed. For this you can use the return callback of the functionHere what your new hooks will looks like with this solution:
That way, the event will be recreated everytime one of the three values is updated.
Update
As you commented, another way to do it is to pass directly the function
handleScroll
inside the dependencies array of the useEffect. But in that case I recommand you to use theuseCallback
hook that will recompute the function only if a value inside the dependencies array has changed (else it will be at each rerender of the component, and that happen a lot)Why does this works ?
I think the best way to explain this is to see the differents states of a component as snapshot. Each time a value change, a new snapshot is created, rewritting the implementation of every function with the values of the states, rerendering the template etc…
So, everytime the state is updated, the implementation of the function of the component will be recomputed with the new values.
Meaning that if i have this state in a component:
And a function like this:
Will actually be computed like this in the snapshot :
So, when you pass the
handleScroll
function as a callback ofwindow.addEventListener
, you are actually passing a function computed with a specific value of the state of the component, so the callback will never be up to date.That is way doing this method works, you are actually passing a different function everytime !