Search code examples
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.