'merging custom hooks for handling scroll

I am trying to merge these two hooks to get the direction and the percentage of the scroll.

useScrollPercentage.js

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

export default function useScrollPercentage() {
  const scrollRef = useRef(null);
  const [scrollPercentage, setScrollPercentage] = useState(NaN);

  const reportScroll = e => {
    setScrollPercentage(getScrollPercentage(e.target));
  };


  useEffect(
    () => {
      const node = scrollRef.current;
      if (node !== null) {
        node.addEventListener("scroll", reportScroll, { passive: true });
        if (Number.isNaN(scrollPercentage)) {
          setScrollPercentage(getScrollPercentage(node));
        }
      }
      return () => {
        if (node !== null) {
          node.removeEventListener("scroll", reportScroll);
        }
      };
    },
    [scrollPercentage]
  );

  return [scrollRef, Number.isNaN(scrollPercentage) ? 0 : scrollPercentage];
}

function getScrollPercentage(element) {
  if (element === null) {
    return NaN;
  }
  const height = element.scrollHeight - element.clientHeight;
  return Math.round((element.scrollTop / height) * 100);
}

useScrollDirection.js

import {useState, useEffect} from 'react';
import _ from 'lodash';

export default function useScrollDirection({
  ref,
  threshold,
  debounce,
  scrollHeightThreshold,
}) {
  threshold = threshold || 10;
  debounce = debounce || 10;
  scrollHeightThreshold = scrollHeightThreshold || 0;
  const [scrollDir, setScrollDir] = useState(null);
  const debouncedSetScrollDir = _.debounce(setScrollDir, debounce);

  useEffect(() => {
    let lastScrollY = ref?.current?.scrollTop;
    let lastScrollDir;
    let ticking = false;
    const hasScrollHeightThreshold =
      ref?.current?.scrollHeight - ref?.current?.clientHeight >
      scrollHeightThreshold;

    const updateScrollDir = () => {
      const scrollY = ref?.current?.scrollTop;
      if (
        Math.abs(scrollY - lastScrollY) < threshold ||
        !hasScrollHeightThreshold
      ) {
        ticking = false;
        return;
      }
      const newScroll = scrollY > lastScrollY ? 'Down' : 'Up';
      if (newScroll !== lastScrollDir) {
        debouncedSetScrollDir(newScroll);
      }
      lastScrollY = scrollY > 0 ? scrollY : 0;
      lastScrollDir = newScroll;
      ticking = false;
    };

    const onScroll = () => {
      if (!ticking) {
        window.requestAnimationFrame(updateScrollDir);
        ticking = true;
      }
    };

    ref?.current?.addEventListener('scroll', onScroll);

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

  return scrollDir;
}

Currently I am using it like this

App.js

 const [scrollRef, scrollPercentage] = useScrollPercentage();
  const scrollDirection = useScrollDirection({ref: scrollRef});

  const handleScroll = () => {
   console.log(scrollPercentage, scrollDirection);
  }

  return (
    <Modal ref={scrollRef} onScroll={handleScroll}>
    // Scrollable Content      
   </Modal>
  )

The problem is that since I don't want to trigger useScrollDirection for every scroll percentage unless the scroll direction changed only it should trigger. Also is it possible to merge these hooks?

Any help is appreciated



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source