Home > OS >  Is there a way infer the return value type of a callback instead of pass the generic parameter
Is there a way infer the return value type of a callback instead of pass the generic parameter

Time:02-17

I had written a react custom hook like this:

import { useEffect, useCallback, useState } from 'react';

const apis = {
    async getUser(id: string): Promise<{ id: string; name: string }> {
        return { id, name: 'test' }
    }
}

type UseAsyncResponseStatus = 'idle' | 'pending';
type AsyncFn = (...args: any[]) => Promise<any>;
type UnboxPromise<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
function useApiQuery<Api extends AsyncFn = AsyncFn>(fn: (...params: any) => ReturnType<Api>, params?: any) {
    const [status, setStatus] = useState<UseAsyncResponseStatus>('idle');
    const [value, setValue] = useState<UnboxPromise<ReturnType<Api>> | null>(null);
    const [error, setError] = useState<Error | null>(null);

    const execute = useCallback(async (_params: any) => {
        try {
            const res = await fn(_params);
            setValue(res);
        } catch (err) {
            setError(err as Error)
        } finally {
            setStatus('idle')
        }
    }, [])

    useEffect(() => {
        execute(params)
    }, [execute, JSON.stringify(params)])

    return { execute, value, status, error }
}

const getUserQuery = useApiQuery<typeof apis.getUser>((id) => {
    return apis.getUser(String(id));
}, '1')
getUserQuery.value?.name // infer the value type based on API response

By now, it works fine if I pass the generic parameter to the useApiQuery, I can get the correct type of the API response. I have many hooks use this hook like this:

const useGetUserQuery = (id) => useApiQuery<typeof apis.getUser>((id) => {
    return apis.getUser(String(id));
}, id)

const useGetUsersQuery = () => useApiQuery<typeof apis.getUsers>(apis.getUsers);

// ...

I found it a little repetitive, I have to pass the generic parameter for each hook. Is there a way that TS can infer the return value type of the callback passed in useApiQuery internally, so that I don't need to pass generic parameters like typeof apis.getUser anymore? If I don't pass the generic parameter, the getUserQuery.value is any type which is not expected.

TypeScript Playground

CodePudding user response:

You can have a type parameter directly for the value instead. You can also add one for the parameters to get type checking on those too:

function useApiQuery<TValue, TParams extends any[]>(fn: (...params: TParams) => Promise<TValue>, ...params: TParams) {
    const [status, setStatus] = useState<UseAsyncResponseStatus>('idle');
    const [value, setValue] = useState<TValue | null>(null);
    const [error, setError] = useState<Error | null>(null);

    const execute = useCallback(async (..._params: TParams) => {
        try {
            const res = await fn(..._params);
            setValue(res);
        } catch (err) {
            setError(err as Error)
        } finally {
            setStatus('idle')
        }
    }, [])

    useEffect(() => {
        execute(...params)
    }, [execute, JSON.stringify(params)])

    return { execute, value, status, error }
}

Playground Link

  • Related