DEV Community

Discussion on: How to make a custom Debounce hook in react js

Collapse
 
loucyx profile image
Lou Cyx

Using TypeScript, you should avoid any as much as possible, so a fixed version would look more like this:

import { useEffect } from "react";
import type { EffectCallback, DependencyList } from "react";

export const useDebouncedEffect = (
    effect: EffectCallback,
    deps: DependencyList,
    delay: number,
) =>
    useEffect(() => {
        const handler = setTimeout(effect, delay);

        return () => clearTimeout(handler);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...deps, delay]);
Enter fullscreen mode Exit fullscreen mode

About the actual implementation, this wouldn’t produce a great DX mainly because we need to pass a dependency list even if we want to runt it "always", so deps ideally should be optional as it is for useEffect, but then we have the problem that you put delay as the last argument, and that one is required. Ideally the argument order should change like this:

import { useEffect } from "react";
import type { EffectCallback, DependencyList } from "react";

export const useDebouncedEffect = (
    effect: EffectCallback,
    delay: number,
    deps?: DependencyList,
) =>
    useEffect(() => {
        const handler = setTimeout(effect, delay);

        return () => clearTimeout(handler);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...deps, delay]);
Enter fullscreen mode Exit fullscreen mode

And you could even make it into a curried function and keep it pretty similar to a regular hook:

import { useEffect } from "react";
import type { EffectCallback, DependencyList } from "react";

export const useDebounce =
    (delay: number) => (effect: EffectCallback, deps?: DependencyList) =>
        useEffect(() => {
            const handler = setTimeout(effect, delay);

            return () => clearTimeout(handler);
            // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [...deps, delay]);
Enter fullscreen mode Exit fullscreen mode

And then you use it like this:

import { useDebounce } from "./useDebounce";

// Outside your component:
const use1SecondDebounce = useDebounce(1_000);

// Inside your component:
use1SecondDebounce(changeSearchState, [search]);
Enter fullscreen mode Exit fullscreen mode

Still, my recommendation would be to use a library for this, like the pretty good use-debounce.

Cheers!

Collapse
 
rajeshroyal profile image
Rajesh Royal • Edited

And you could even make it into a curried function and keep it pretty similar to a regular hook:

Not useful in this case, yes we can suppress TS warning but It will not be a good idea.

React Hook useEffect cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.

React Hook useDebouncedEffect cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.

Collapse
 
loucyx profile image
Lou Cyx • Edited

The linter doesn't know how are you using your code so it gives you those warnings, if you use it as I said (first call of the curried version outside the component, second inside) it works exactly the same as a regular hook. Linters are there to help, but they aren't silver bullets, you have to understand the reason for the warnings and errors you get.

Collapse
 
rajeshroyal profile image
Rajesh Royal • Edited

Didn't tried the use-debounce but the loadsh's Debounce was having some state persistence issue.

Your comment is super helpful, thanks man 🤘