Search code examples
reactjsinfinite-scroll

React scroll event that has access to State


I'm trying to build an infinite scroll component in React (specifically using NextJS). I am having trouble with this feature because when I set a scroll event on the window, it doesn't have access to updated state. How can I write a scroll event that listens to any scrolling on the entire window that also has access to state like router query params?

Here's some code to see what I'm trying to do:

 useEffect(() => {
    window.addEventListener('scroll', handleScroll);
  },[]);

  const handleScroll = () => {
    const el = infiniteScroll.current;
    if (el) {
      const rect = el.getBoundingClientRect();
      const isVisible =
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <=
          (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <=
          (window.innerWidth || document.documentElement.clientWidth);

      if (isVisible && !isComplete && !isFetching) {
        nextPage();
      }
    }
  };

  const nextPage = () => {
    const params = router.query as any;  // <------ these params here never update with state and are locked in to the the values they were at when the component mounted
    params.page = params.page
      ? (parseInt((params as any).page) + 1).toString()
      : '1';
    router.replace(router, undefined, { scroll: false });
  };

The issue is that the router value is locked at the place it was when the component mounted.

I've tried removing the empty array of dependencies for the useEffect at the top, but as you can imagine, this creates multiple scroll listeners and my events fire too many times. I've tried removing the eventListener before adding it every time, but it still fires too many times.

Every example I've found online seems to not need access to state variables, so they write code just like this and it works for them.

Any ideas how I can implement this?

I've tried to use the onScroll event, but it doesn't work unless you have a fixed height on the container so that you can use overflow-y: scroll.


Solution

  • You can use a ref to access and modify your state in the scope of the handleScroll function.

    Here is how:

    const yourRef = useRef('foo');
    
    useEffect(() => {
        const handleScroll = () => {
            const value = yourRef.current;
    
            if (value === 'foo') {
                yourRef.current = 'bar'
            }
        };
    
        window.addEventListener('scroll', handleScroll);
        return () => {
            window.removeEventListener('scroll', handleScroll);
        };
    }, []);