Harrison Cramer
Harrison Cramer

Reputation: 4506

Using React Hooks To Update w/ Scroll

I'm trying to control the visibility of a React Component based on whether an individual is scrolling down on the component. The visibility is passed into the Fade element as the "in" property.

I've set up a listener using the UseEffect Hook, which adds the listener onMount. The actual onScroll function is supposed to update the scrollTop state (which is the current value of the height to the top of the page) and then the scrolling state (which compares the event's scroll to the top of the page with the previous state, and if the first is greater than the second, returns true).

However, for some reason the setScrollTop hook isn't working, and the scrolling state continues to stay at 0.

What am I doing wrong? Here's the full component:

export const Header = (props) => {

  const classes = useStyles();

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

  const onScroll = (e) => {
    setScrollTop(e.target.documentElement.scrollTop);
    setScrolling(e.target.documentElement.scrollTop > scrollTop);
  }

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

  useEffect(() => {
    console.log(scrollTop);
  }, [scrollTop])

  return (
   <Fade in={!scrolling}>
      <AppBar className={classes.header} position="fixed">

  ....

Upvotes: 33

Views: 75508

Answers (2)

Ili
Ili

Reputation: 711

Or you can use window.pageYOffset. It's a bit more understandable for me that way:

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

  useEffect(() => {
    function onScroll() {
      let currentPosition = window.pageYOffset; // or use document.documentElement.scrollTop;
      if (currentPosition > scrollTop) {
        // downscroll code
        setScrolling(false);
      } else {
        // upscroll code
        setScrolling(true);
      }
      setScrollTop(currentPosition <= 0 ? 0 : currentPosition);
    }

    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, [scrollTop]);

Upvotes: 9

Clarity
Clarity

Reputation: 10873

You're missing the dependencies in your hook. Try this:

  useEffect(() => {
    const onScroll = e => {
      setScrollTop(e.target.documentElement.scrollTop);
      setScrolling(e.target.documentElement.scrollTop > scrollTop);
    };
    window.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
  }, [scrollTop]);

By moving onScroll inside the useEffect, you don't need to track it on the hook's dependencies, however since it uses scrollTop from the component's scope, you'll need to add it.

Alternatively, if for some reason you don't want to move onScroll definition inside the useEffect, you'll need to wrap onScroll in useCallback and track it in useEffect's dependency array.

In general I'd recommend adding react-hooks/exhaustive-deps to your ESlint rules

Also it's a good idea to remove the event listener in cleanup function.

Upvotes: 51

Related Questions