UseEffect won’t use updated state

Tags: ,



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.

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).



Source: stackoverflow