Example:
function useCallback(fn) {
return fn;
}
type ApiData = {
'/user': { user: any },
'/post': { post: any },
};
function useApi<Path extends keyof ApiData>(
path: Path,
opts: {
cb?: (data: ApiData[Path]) => void,
},
) {}
useApi('/user', { cb: ({ user }) => null }); // ok
useApi('/user', { cb: ({ post }) => null }); // Property 'post' does not exist on type '{ user: any; }'
useApi('/user', { cb: useCallback(({ user }) => null) }); // ok
useApi('/user', { cb: useCallback(({ post }) => null) }); // should have error
TS Playground
In this example, without useCallback, TS was able to automatically determine the type of the callback's argument's type. However, with useCallback, it loses that ability.
Interestingly, if I make the cb property required, then TS is able to infer the type of the argument:
function useApi<Path extends keyof ApiData>(
path: Path,
opts: {
cb: (data: ApiData[Path]) => void, // <- removed the "?"
},
) {}
useApi('/user', { cb: useCallback(({ post }) => null) }); // Property 'post' does not exist on type '{ user: any; }'
How can I make TS infer the argument of the callback for an optional argument wrapped in useCallback?
Edit:
In 2025, TS's behavior has changed, but I'm still encountering a similar issue. Now, the param for the callback is typed as any, so the following case shows Binding element 'user' implicitly has an 'any' type.:
useApi('/user', { cb: useCallback(({ user }) => null) });
Is it not as simple as this?
function identity<F>(fn: F): F {
return fn;
}
Even this seems to be enough to get tsc to get the hint:
function identity<F>(fn: F) {
return fn;
}
This is also an option:
function identity<F>(fn: F): typeof fn {
return fn;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With