React Hooks, how to implement useHideOnScroll hook

2019-07-29 03:29发布

const shouldHide = useHideOnScroll();
return shouldHide ? null : <div>something</div>

The useHideOnScroll behaviour should return updated value not on every scroll but only when there is a change.

The pseudo logic being something like the following:

if (scrolledDown && !isHidden) {
        setIsHidden(true);
      } else if (scrolledUp && isHidden) {
        setIsHidden(false);
      }

In words, if scroll down and not hidden, then hide. If scroll up and hidden, then unhide. But if scroll down and hidden, do nothing or scroll up and not hidden, do nothing.

How do you implement that with hooks?

3条回答
别忘想泡老子
2楼-- · 2019-07-29 04:11

After hours of dangling, here is what I came up with.

const useHideOnScroll = () => {
  const [isHidden, setIsHidden] = useState(false);
  const prevScrollY = useRef<number>();

  useEffect(() => {
    const onScroll = () => {
      const scrolledDown = window.scrollY > prevScrollY.current!;
      const scrolledUp = !scrolledDown;

      if (scrolledDown && !isHidden) {
        setIsHidden(true);
      } else if (scrolledUp && isHidden) {
        setIsHidden(false);
      }

      prevScrollY.current = window.scrollY;
    };

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

  return isHidden;
};

Usage:

const shouldHide = useHideOnScroll();
return shouldHide ? null : <div>something</div>

It's still suboptimal, because we reassign the onScroll when the isHidden changes. Everything else felt too hacky and undocumented. I'm really interested in finding a way to do the same, without reassigning onScroll. Comment if you know a way :)

查看更多
叼着烟拽天下
3楼-- · 2019-07-29 04:14

You need to use window.addEventListener and https://reactjs.org/docs/hooks-custom.html guide.

That is my working example:

import React, { useState, useEffect } from "react";

const useHideOnScrolled = () => {
  const [hidden, setHidden] = useState(false);

  const handleScroll = () => {
    const top = window.pageYOffset || document.documentElement.scrollTop;
    setHidden(top !== 0);
  };

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

  return hidden;
};

export default useHideOnScrolled;

live demo: https://codesandbox.io/s/w0p3xkoq2l?fontsize=14

and i think name useIsScrolled() or something like that would be better

查看更多
ら.Afraid
4楼-- · 2019-07-29 04:25

Here:

const useHideOnScroll = () => {
  const prevScrollY = React.useRef<number>();
  const [isHidden, setIsHidden] = React.useState(false);

  React.useEffect(() => {
    const onScroll = () => {
      setIsHidden(isHidden => {
        const scrolledDown = window.scrollY > prevScrollY.current!;
        if (scrolledDown && !isHidden) {
          return true;
        } else if (!scrolledDown && isHidden) {
          return false;
        } else {
          prevScrollY.current = window.scrollY;
          return isHidden;
        }
      });
    };

    console.log("adding listener");
    window.addEventListener("scroll", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return isHidden;
};

const Navbar = () => {
  const isHidden = useHideOnScroll();
  console.info("rerender");
  return isHidden ? null : <div className="navbar">navbar</div>;
};

export default Navbar;

You might have concern about setIsHidden causing rerender on every onScroll, by always returning some new state value, but a setter from useState is smart enough to update only if the value has actually changed.

Also your .navbar (I've added a class to it) shouldn't change the layout when it appears or your snippet will get locked in an infinite loop. Here're appropriate styles for it as well:

.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 30px;
  background: rgba(255, 255, 255, 0.8);
}

Full CodeSandbox: https://codesandbox.io/s/13kr4xqrwq

查看更多
登录 后发表回答