useDebounceUpdate - Running an effect after a timeout

17 December 2021

2 min read

Web development

Execute a function after a timeout every time your data changes. Reset the timer if the data changes again during the timeout.

Sometimes when the user updates some state, we want to make an API request to sync the state remotely. We cannot run a function every time the user changes the data however.

What we do instead is debounce the state update, i.e. waiting a fixed amount of time after the user stops interacting before making the request. Furthermore, we want to reset the timeout if in the meantime the user changes the state again.

Well, that's what useDebounceUpdate is made for!

isFirstRender is used to avoid running execute at first render (which is probably something that you don't want here 💁🏼‍♂️).

Every time data changes timer resets and the execute function will be called again only after timeout milliseconds.

useDebounceUpdate.ts
import { useEffect, useRef } from "react";

/**
 * Run the `execute` function after `timeout` milliseconds.
 * If the value of `data` changes, reset the timer.
 */
export const useDebounceUpdate = <Data>({
  execute,
  data,
  timeout = 2000,
}: {
  data: Data;
  timeout?: number;
  execute: (value: Data) => void;
}): void => {
  const isFirstRender = useRef(true);
  const timer = useRef<ReturnType<typeof setTimeout>>();
  useEffect(() => {
    if (!isFirstRender.current) {
      if (timer.current) {
        clearTimeout(timer.current);
      }

      timer.current = setTimeout(() => {
        execute(data);
      }, timeout);
    } else {
      isFirstRender.current = false;
    }
  }, [data]);
};