reactjsscrolldebouncingonscroll

How to debounce 'onScroll' in react without using other libraries


the code below logs 'scrolled' to the console multiple times (at least 10 times), even though using monitorEvents() indicates a single keydown event.

how can i ensure only a single 'scrolled' gets logged to the console without using external libraries

(i wish to cover all possible events that trigger a scroll, hence why i am targeting scroll and not keydown)

import "./App.css";

function App() {
  return (
    <section
      onClick={() => console.log("clicked")}
      onScroll={() => console.log("scrolled")}
      style={{ height: "100vh", width: "100vw", overflow: "scroll" }}
    >
      <p style={{ height: "300vh" }}>hi</p>
    </section>
  );
}

export default App;

Solution

  • I don't know your initial purpose. But I can suggest the following solutions:

    1. If you need to be sure if the scroll was used somehow at least once, you can simply use the component state to identify it.

    function App() {
      const [isScrolled, setScrolled] = useState(false);
    
      const onScroll = () => {
        if (!isScrolled) {
          setScrolled(true);
          console.log("onFirstScroll");
        }
      };
    
      return (
        <section
          onClick={() => console.log("clicked")}
          onScroll={onScroll}
          style={{ height: "100vh", width: "100vw", overflow: "scroll" }}
        >
          <p style={{ height: "300vh" }}>hi</p>
        </section>
      );
    }

    1. If you need to debounce the scroll, you can create a custom hook which will debounce any calls for passed function.

    const useDebouncedCallback = (callback, delay) => {
      const callbackRef = useRef(callback);
      const timerRef = useRef();
      
      return useCallback(
        (...args) => {
          if (timerRef.current) {
            clearTimeout(timerRef.current);
          }
    
          timerRef.current = setTimeout(() => {
            callbackRef.current(...args);
          }, delay);
        },
        []
      );
    };
    
    function App() {
      const onScroll = useDebouncedCallback(() => {
        console.log("On scroll debounced (100ms)");
      }, 100)
    
      return (
        <section
          onClick={() => console.log("clicked")}
          onScroll={onScroll}
          style={{ height: "100vh", width: "100vw", overflow: "scroll" }}
        >
          <p style={{ height: "300vh" }}>hi</p>
        </section>
      );
    }

    This is just an example of a simple hook that might be improved, but I hope you can use it at the first step.