State does get set on the scroll, but logged from the eventlistener, it seems to be stuck at the initial value.
I guess it's something to do with scrolling
being set when the side effect's defined, but how could I trigger a state change from a scroll otherwise? Same goes for any window event I presume.
Here's a codesandbox example: https://codesandbox.io/s/react-test-zft3e
const [scrolling, setScrolling] = useState(false);
useEffect(() => {
window.addEventListener("scroll", () => {
console.log(scrolling);
if (scrolling === false) setScrolling(true);
});
}, []);
return (
<>
scrolling: {scrolling}
</>
);
So your anonymous function is locked on initial value of scrolling
. It's how closures works in JS and you better find out some pretty article on that, it may be tricky some time and hooks heavily rely on closures.
So far there are 3 different solutions here:
useEffect(() => {
const scrollHandler = () => {
if (scrolling === false) setScrolling(true);
};
window.addEventListener("scroll", scrollHandler);
return () => window.removeEventListener("scroll", scrollHandler);
}, [scrolling]);
while following this path ensure your are returning cleanup function from useEffect
. It's good default approach but for scrolling it may affect performance because scroll event triggers too often.
const scrolling = useRef(false);
useEffect(() => {
const handler = () => {
if (scrolling.current === false) scrolling.current = true;
};
window.addEventListener("scroll", handler);
return () => window.removeEventListener("scroll", handler);
}, []);
return (
<>
scrolling: {scrolling}
</>
);
downside: changing ref does not trigger re-render. So you need to have some other variable to change it triggering re-render.
(I see it as preferred way here):
useEffect(() => {
const scrollHandler = () => {
setScrolling((currentScrolling) => {
if (!currentScrolling) return true;
return false;
});
};
window.addEventListener("scroll", scrollHandler);
return () => window.removeEventListener("scroll", scrollHandler);
}, []);
Note Btw even for one-time use effect you better return cleanup function anyway.
PS Also by now you don't set scrolling
to false
, so you could just get rid of condition if(scrolling === false)
, but sure in real world scenario you may also run into something alike.
This means that the variable at that moment are also "trapped" at that point, since on rerender you're not reinitializing the event listener.
It's kind of like a snapshot of the on mount moment.
If you move the console.log outside you will see it change as the rerenders happen and set the scroll value again.
const [scrolling, setScrolling] = useState(false);
useEffect(() => {
window.addEventListener("scroll", () => {
if (scrolling === false) setScrolling(true);
});
}, []);
console.log(scrolling);
return (
<>
scrolling: {scrolling}
</>
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With