Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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">

  ....
like image 267
Harrison Cramer Avatar asked Aug 11 '19 19:08

Harrison Cramer


Video Answer


2 Answers

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.

like image 77
Clarity Avatar answered Oct 22 '22 02:10

Clarity


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]);
like image 9
Ili Avatar answered Oct 22 '22 02:10

Ili