How to use throttle or debounce with React Hook?

Alexandre Annic picture Alexandre Annic · Feb 13, 2019 · Viewed 52.6k times · Source

I'm trying to use the throttle method from lodash in a functional component, e.g.:

const App = () => {
  const [value, setValue] = useState(0)
  useEffect(throttle(() => console.log(value), 1000), [value])
  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

Since the method inside useEffect is redeclared at each render, the throttling effect does not work.

Does anyone have a simple solution ?

Answer

skyboyer picture skyboyer · Feb 13, 2019

After some time passed I'm sure it's much easier to handle things by your own with setTimeout/clearTimeout(and moving that into separate custom hook) than working with functional helpers. Handling later one creates additional challenges right after we apply that to useCallback that can be recreated because of dependency change but we don't want to reset delay running.

original answer below

you may(and probably need) useRef to store value between renders. Just like it's suggested for timers

Something like that

const App = () => {
  const [value, setValue] = useState(0)
  const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))

  useEffect(() => throttled.current(value), [value])

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

As for useCallback:

It may work too as

const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);

But if we try to recreate callback once value is changed:

const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);

we may find it does not delay execution: once value is changed callback is immediately re-created and executed.

So I see useCallback in case of delayed run does not provide significant advantage. It's up to you.

[UPD] initially it was

  const throttled = useRef(throttle(() => console.log(value), 1000))

  useEffect(throttled.current, [value])

but that way throttled.current has bound to initial value(of 0) by closure. So it was never changed even on next renders.

So be careful while pushing functions into useRef because of closure feature.