Igor Shmukler
Igor Shmukler

Reputation: 2216

React app does not fire off onScroll events as expected

I am trying to show a specific component - a "sticky header" piece when the page displaying it, is scrolled X number of points off the top.

I tried doing this with the useWindowScroll hook from the use-react package, as well with a short fragment below:

  const [scrollTop, setScrollTop] = useState(0);

  function setScroll() {
    console.log('setScroll');
    setScrollTop(window.pageYOffset);
    console.log(new Date().getTime());
  }

  useEffect(() => {
    function watchScroll() {
      console.log('watchScroll');
      window.addEventListener("scroll", setScroll);
    }
    watchScroll();
    return () => {
      window.removeEventListener("scroll", setScroll);
    };
  });

  console.log('scroll top:', scrollTop);

Ideally, we probably want to have a bit of a delay before each new scroll event is processed, yet it is not my problem.

I would expect setScroll to get called whenever I scroll, every however many millisecond. However, this is NOT happening. In fact, I don't see it called ever.

Since it is not being called when approriate, my const shouldShowStickyHeader = scrollTop > 1000; is always set to false. Hence, my is never shown. I want it to appear when the scroll to top distance is 1,000.

What is the problem?

I also tried version with everything inside the effects hook as:

  const [scrollTop, setScrollTop] = useState(0);

  useEffect(() => {
    function setScroll() {
      console.log('setScroll');
      setScrollTop(window.pageYOffset);
      console.log(new Date().getTime());
    }
    window.addEventListener("scroll", setScroll);
    return () => {
      window.removeEventListener("scroll", setScroll);
    };
  }, [scrollTop]);

I don't see setScroll being printed in the console. What could be the problem?

I did a little test with document.querySelectorAll("*").forEach(element => element.addEventListener("scroll", ({target}) => console.log(target, target?.id, target?.parent, target?.parent?.id)));. There are indeed events being fired off when I scroll. See:

<div data-testid="vdp-scroll-container" id="vdp-scroll-container" class="jsx-1959376998 vdp-scroll-container">
...
<div data-testid="vdp-scroll-container" id="vdp-scroll-container" class="jsx-1959376998 vdp-scroll-container">

I tried putting listers directly on this element with:

document.getElementById('#vdp-scroll-container')?.addEventListener("scroll", setScroll);
    return () => {
      document.getElementById('#vdp-scroll-container')?.removeEventListener("scroll", setScroll);
    };

Still, I don't see anything getting fired off.

The styles on this <div/> are below:

height: 100%;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow-y: scroll;

Am I not getting anything because of that?

Upvotes: 3

Views: 7916

Answers (2)

Igor Shmukler
Igor Shmukler

Reputation: 2216

Attaching the listener to window did not work. However, after I removed height: 100%, I was able to subscribe to the scroll event with:

  const [scrollTop, setScrollTop] = useState(0);

  useEffect(() => {
    const setScroll = () => {
      setScrollTop(window.pageYOffset);
    };
    document.querySelector('#vdp-scroll-container')?.addEventListener("scroll", setScroll);
    return () => {
      document.querySelector('#vdp-scroll-container')?.removeEventListener("scroll", setScroll);
    };
  }, [scrollTop]);

Upvotes: 3

Max
Max

Reputation: 1567

You should pass an empty array to useEffect as the second argument so that the function is fired only after the component mounts for the first time. Not passing a second argument, will trigger the function after every rerender and passing [scrollTop] will trigger the function every time scrollTop changes.

Something like this should work:

  const [offset, setOffset] = React.useState(null);
  const setScroll = () => {
    setOffset(window.scrollY);
  };

  React.useEffect(() => {
    window.addEventListener("scroll", setScroll);
    return () => {
      window.removeEventListener("scroll", setScroll);
    };
  }, []);

Working example: https://codesandbox.io/s/zen-cdn-ll6es?file=/src/App.js

Upvotes: 3

Related Questions