Search code examples
javascriptreactjsaddeventlistenerparallaxuse-effect

React, useEffect cleanup not working with removeEventListener, useRef, parallax effect


I am trying to apply a parallax effect to an .svg image by using useRef() to grab bubblesRef and translateY() onScroll.

The parallax works but when I navigate to the next page I receive error "TypeError: Cannot read property 'style' of null". I think it is because the addEventListener is still listening and trying to useRef() on bubblesRef while navigating to the next page. So I added the cleanup function in useEffect() but that doesn't seem to fix it.

Any help is appreciated. Thanks!

p.s. If anyone can share their approach to a simple parallax effect like this that would be great too. This is the only approach I've figured that won't rerender everything else on the page onScroll.

const HomePage = () => {
  const [loadedPosts, setLoadedPosts] = useState([]);
  const { sendRequest } = useHttpClient();
  console.log("loadedPosts homePage", loadedPosts);
  const bubblesRef = useRef();

  useEffect(() => {
    if (loadedPosts.length === 0) {
      //api call
    }
  }, [sendRequest, loadedPosts]);

  useEffect(() => {
    const parallax = () => {
      let scrolledValue = window.scrollY / 3.5;
      bubblesRef.current.style.transform = `translateY(
      -${scrolledValue + "px"} 
      )`;
      console.log("scrolling...", scrolledValue);
    };
    window.addEventListener("scroll", parallax);
    return () => window.removeEventListener("scroll", parallax);
  }, []);

  return (

      <HomePageContainer>
        <Header />
        <SectionOne posts={loadedPosts} />
        <SectionTwo />
         <BubbleBlobs className="bubbleBlobs" ref={bubblesRef} />
        <BlobTop className="backBlobBottom" preserveAspectRatio="none" />
      </HomePageContainer>
   );
};

export default HomePage;

Solution

  • You definitely need the cleanup function any time you add a listener to the window, or the handler (and thus the component instance itself) will live on forever. However, since React runs those cleanup hooks asynchronously, it might not happen until after other window events. The value of the ref is set to null when the component unmounts, so you need to check that it is still defined before using the value.

    useEffect(() => {
      const handler = () => {
        if (ref.current) {
          // perform update
        }
      }
    
      window.addEventListener('scroll', handler)
      return () => window.removeEventListener('scroll', handler)
    }, [])