Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: can't infer type for optional arguments passed through an identity function

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) });
like image 389
Leo Jiang Avatar asked Nov 29 '25 08:11

Leo Jiang


1 Answers

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;
}
like image 89
starball Avatar answered Dec 01 '25 21:12

starball