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.
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;
},
});
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;
};