简体   繁体   中英

React: How do I prevent the usage of useRef and useState to keep track of a same value in order to prevent useEffect from triggering?

currently I am making a navbar that only shows when you scroll up, to prevent useEffect to run everytime when the visible state get changed, I had to use both a ref and a state that is synced together to do comparison in the useEffect, using ref and a state to keep track of a same value seems extremely fishy, is there another way of doing this? one that does not involve triggering useEffect from creating the event handlers everytime the state changes?

import React, { useState, useRef, useEffect } from 'react';
import Link from 'next/link';

const NavbarLink = ({ name, href }: { name: string, href: string }) => {
  return (
    <Link href={href}>
      <a>{ name }</a>
    </Link>
  );
}

const Navbar = () => {
  const scrollYRef = useRef(0);
  const visibleRef = useRef(true);
  const [visible, setVisible] = useState(true);

  useEffect(() => {
    const onScroll = (event: Event) => {
      event.preventDefault();
      if ((window.scrollY < scrollYRef.current) != visibleRef.current) {
        visibleRef.current = !visibleRef.current;
        setVisible(x => !x);
      }
      scrollYRef.current = window.scrollY;
    }

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

  return (
    <div className={`${!visible && '-translate-y-full'} fixed flex w-full h-32 font-bold text-white transition-all`}>
      <NavbarLink name="home" href='/'/>
    </div>
  );
}

You could use the "latest ref" pattern (here via react-use 's useLatest hook ), which boxes the latest value of a state atom at each component update time, so you don't need to manage it manually, and you don't need it as a dependency for the hook. (I'd also grab useEvent from react-use if you end up using it.)

import React, { useState, useRef, useEffect } from "react";
import Link from "next/link";

// via https://github.com/streamich/react-use/blob/master/src/useLatest.ts
const useLatest = (value) => {
  const ref = useRef(value);
  ref.current = value;
  return ref;
};

const NavbarLink = ({ name, href }: { name: string; href: string }) => {
  return (
    <Link href={href}>
      <a>{name}</a>
    </Link>
  );
};

function useVisibleOnScrollUp(initial = true) {
  const scrollYRef = useRef(window.scrollY);
  const [visible, setVisible] = useState(initial);
  const latestVisibleRef = useLatest(visible);

  useEffect(() => {
    const onScroll = (event: Event) => {
      event.preventDefault();
      const currentVisible = latestVisibleRef.current;
      if (window.scrollY < scrollYRef.current != currentVisible) {
        setVisible((x) => !x);
      }
      scrollYRef.current = window.scrollY;
    };

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

const Navbar = () => {
  const visible = useVisibleOnScrollUp(true);
  return (
    <div
      className={`${
        !visible && "-translate-y-full"
      } fixed flex w-full h-32 font-bold text-white transition-all`}
    >
      <NavbarLink name="home" href="/" />
    </div>
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM