Search code examples
typescript

TypeScript generic is not inferred correctly inside of function body


I have a component that accepts an IsClearable generic and then uses that generic in a conditional type to determine the arguments to its onChange function. This seems to work when I call the function. But inside of the function itself, I can't get it to properly narrow the type, so if I check that isClearable is true, it still uses the false branch from the conditional type.

TS Playground

interface Props<IsClearable extends boolean = false> {
  isClearable?: IsClearable;
  onChange: IsClearable extends true
    ? (v?: number) => number
    : (v: number) => number;
}

function Thing<IsClearable extends boolean = false>({
  isClearable,
  onChange,
}: Props<IsClearable>): number {
  if (isClearable) {
    onChange(undefined)  // <-- Why does this complain?
  } else {
    onChange(1)
  }
  return 1;
}

Thing({
  // v is number, which is correct
  onChange: (v) => {
    return 1;
  },
});

Thing({
  isClearable: true,
  // v is number | undefined, which is correct
  onChange: (v) => {
    return 1;
  },
});

Solution

  • This is a known design limitation of TypeScript. You cannot narrow an object's type by a property unless you're working with discriminated unions. You can refactor Props to something like this:

    type Props<IsClearable extends boolean = false> = IsClearable extends true
      ? {
          isClearable: IsClearable;
          onChange: (v?: number) => number;
        }
      : {
          isClearable?: IsClearable;
          onChange: (v: number) => number;
        };
    

    TypeScript Playground