DEV Community

Cover image for Custom React hook : useDebouncedEffect()
TusharShahi
TusharShahi

Posted on

Custom React hook : useDebouncedEffect()

Background

Recently I saw a question on Stackoverflow where the poster was looking to run a useEffect in debounced manner.

Here is a screenshot:

The original question

This makes sense as many times as there are situations where some effects need to run but not on every change to a dependency. Eg: In a type search bar which makes API calls, we can run useEffect in a debounced manner to optimise resource utilisation.

I tried to have a go at the answer and tried making a custom hook to solve the problem. I will try to summarise the hook below:

The hook

This is the basic structure we know our custom hook would follow:

const useDebounceEffect = (fnc, deps, delay) => {

 useEffect(() => {


 },deps);

}
Enter fullscreen mode Exit fullscreen mode

In addition to the params of the standard useEffect, this will have a delay parameter too. The hook internally will still use useEffect.

We will need to use a setTimeout to schedule our callback function. This timeout needs to be cleared too, so it is important to keep the ID of the timeout. We need to have a ref which holds our timeout ID. State variable is not required here since we do not want to cause a re-render whenever our ID changes.

const useDebounceEffect = (fnc, deps, delay) => {
  const ref = useRef();

  useEffect(() => {
    clearTimeout(ref.current);
    ref.current = setTimeout(() => {
      fnc();
      clearTimeout(ref.current);
    }, delay);
  }, deps);
};
Enter fullscreen mode Exit fullscreen mode

The hook looks almost ready, but there is one problem:
The dependencies are not complete. If after a render, fnc changes or delay changes, the effect is not triggered.

We get a warning for that from our linter too and we fix it to complete the hook.

const useDebounceEffect = (fnc, deps, delay) => {
  const ref = useRef();

  useEffect(() => {
    clearTimeout(ref.current);
    ref.current = setTimeout(() => {
      fnc();
      clearTimeout(ref.current);
    }, delay);
  }, [fnc, ...deps,delay]);
};
Enter fullscreen mode Exit fullscreen mode

Note how we do not need to mention ref in the dependency. ref.current will always have the correct value since it is a mutable container and changing ref.current does not cause a rerender.

Links

With this our hook is complete. Here is link to the original question and a running demo

Top comments (0)