Search code examples
reactjstypescriptdebouncing

Typescript strongly type a function with dynamic parameters passed to another function (React - debounce hook)


I'm trying to strongly type a function (F1) that is passed to another function (F2). F1 has a dynamic amount of parameters so I cannot define any specific type.

The function/hook I'm working on:

export function useDebounceVoidFunction(func: (...params: any[]) => void, timeout: number): (...params: any[]) => void { // F2

    let timer: MutableRefObject<NodeJS.Timeout | undefined> = useRef();

    const debouncedFunction = useCallback((...params: any[]) => { // F1
        clearTimeout(timer.current);
        timer.current = setTimeout(() => {
            func(...params);
        }, timeout);
    }, [func, timeout])

    return debouncedFunction; // F1
}

This kind of works... but the parameters in F1 are now any. This mean I could supply a function with 2 parameters and then use the F1 with as many parameters as I want.

I tried using the Wildcard type (useDebounceVoidFunction<T>(func: (...params: T[]) ... but that doesn't work. I think T at that time is of type unknown.

Any suggestions on how I could solve this? Couldn't find any post that resolved my issue.

EDIT1: Added small playground and trying to use Parameter Playground


Solution

  • Your function needs to be generic in the tuple type corresponding to the function's parameter list. You can give a function a rest parameter of such a generic type:

    function useDebounceVoidFunction<A extends any[]>
        (func: (...params: A) => void, timeout: number): (...params: A) => void {
    
        // let timer: MutableRefObject<NodeJS.Timeout | undefined> = useRef();
    
        return (...params: A) => {
            func(...params);
        };
    }
    

    Here, A is a generic type parameter which has been constrained to an arraylike type (A for Arguments). And both the func callback and the return type have a params rest argument of type A.

    When you call useDebounceVoidFunction(), the compiler will infer a tuple type for A corresponding to the ordered list of parameter types to the func argument:

    function someFunc(number: number, string: string) {
        console.log(number.toFixed(2), string.toUpperCase())
    }
    
    function coolStuff() {
        const debouncedFunction = useDebounceVoidFunction(someFunc, 200);
        debouncedFunction(Math.PI, "abc") // "3.14", "ABC"
    }
    

    Playground link to code