Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't throttle function with useState hook

I'm trying to throttle an event "wheel" on scroll using a lodash library for my React app with no success.

I need to listen to e.deltaY from scroll input in order to detect its scroll direction. To add a listener I wrote a React hook that accepts an event name and a handler function.

Base implementation

  const [count, setCount] = useState(0);

  const handleSections = () => {
    setCount(count + 1);
  };

  const handleWheel = _.throttle(e => {
    handleSections();
  }, 10000);

  useEventListener("wheel", handleWheel);

My useEventListener hook

function useEventListener(e, handler, passive = false) {
  useEffect(() => {
    window.addEventListener(e, handler, passive);

    return function remove() {
      window.removeEventListener(e, handler);
    };
  });
}

Working demo: https://codesandbox.io/s/throttledemo-hkf7n

My goal is to throttle this scroll event in order to have less events firing and to have a few seconds to scroll my page programmatically (scrollBy(), par example). At this moment throttling seem to not work so I'm getting lots of scroll events all at once

like image 504
moshyfawn Avatar asked May 19 '26 17:05

moshyfawn


1 Answers

When you can call _.throttle() on a function, you get back a new function that "manages" the invocation of the original function. Whenever the wrapper function is called, the wrapper checks if enough time passed, and if so it calls the original function.

If _.throttle() is called multiple times, it returns a new function that doesn't have the "history" of calling the function. It will then call the original function again and again.

In your case the wrapped function is regenerated on each render. Wrap the call to _.throttle() with useCallback (sandbox):

const { useState, useCallback, useEffect } = React;

function useEventListener(e, handler, cleanup, passive = false) {
  useEffect(() => {
    window.addEventListener(e, handler, passive);

    return function remove() {
      cleanup && cleanup(); // optional specific cleanup for the handler
    
      window.removeEventListener(e, handler);
    };
  }, [e, handler, passive]);
}

const App = () => {
  const [count, setCount] = useState(0);

  const handleWheel = useCallback(_.throttle(() => {
    setCount(count => count + 1);
  }, 10000, { leading: false }), [setCount]);

  useEventListener("wheel", handleWheel, handleWheel.cancel); // add cleanup to cancel throttled calls

  return (
    <div className="App">
      <h1>Event fired {count} times</h1>
      <h2>It should add +1 to cout once per 10 seconds, doesn't it?</h2>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.App {
  font-family: sans-serif;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
like image 75
Ori Drori Avatar answered May 22 '26 08:05

Ori Drori