I have a react function which is supposed to be my header. This header shall change its background color after reaching a button on scroll.
To do so I use a scroll event listener and track its position in relation to the button. This works fine for setTransparent(false)
, but not for setTransparent(true)
:
Logging transparent
inside of the listener returns true
even after setting it to false
inside of the first if-statement.
How so? What’s the best practice here?
const [transparent, setTransparent] = useState(true); useEffect(() => { const button = document.querySelector(".hero-button"); window.addEventListener("scroll", () => { const {bottom} = button.getBoundingClientRect(); if (transparent && bottom <= 0) { setTransparent(false); } else if (!transparent && bottom > 0) { setTransparent(true); } }); }, [])
Setting the dependency to transparent
will make it work, but this will add even listener every time it updates.
Advertisement
Answer
Your transparent
variable in the effect callback only references the value on the initial render, which is always true
. You can fix it by re-adding the scroll listener whenever transparent
changes, and return a cleanup function that removes the prior handler:
useEffect(() => { const button = document.querySelector(".hero-button"); const scrollHandler = () => { const { bottom } = button.getBoundingClientRect(); if (transparent && bottom <= 0) { setTransparent(false); } else if (!transparent && bottom > 0) { setTransparent(true); } }; window.addEventListener("scroll", scrollHandler); // DON'T FORGET THE NEXT LINE return () => window.removeEventListener("scroll", scrollHandler); }, [transparent]);
Another option would be to use a ref instead of useState
for transparent (or, in addition to useState
if transparent
changes needs to result in re-rendering).