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.
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 }
}