Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - Typescript - narrow down callback type based on optional boolean prop

I have a <Selector /> component which can accept an optional param isMulti: boolean. It also have a required param onSelected, who's signature should be changed based on isMulti value (wheater is true or not - undefined)

I've created an example here

My typescript implementation fails somewhere in the following implementation (I think):

interface ISelectorProps<T> {
  isMulti?: T;
  onSelected: T extends true
    ? ((options: string[]) => void)
    : ((option: string) => void);
}

So the idea is that, if isMulti is provided, onSelected should return an array of strings, otherwise, it should provide only a string;

As you can see in the editor, TS complains here:

enter image description here

Argument of type 'string[]' is not assignable to parameter of type 'string & string[]'.
  Type 'string[]' is not assignable to type 'string'.

The signature of onSelected is wrongly typed. Any ideas?

like image 231
stackoverflow Avatar asked Sep 06 '25 07:09

stackoverflow


1 Answers

The simple thing to do is to define your type as either/or with the isMulti being the selector. This is called a discriminated union

type ISelectorProps = {
  isMulti: true;
  onSelected: (options: string[]) => void;
} | {
  isMulti: false;
  onSelected: (option: string) => void;
}

function x(props: ISelectorProps) {
    if (props.isMulti === true) {
        props.onSelected(['hey', 'there']);
    } else {
        props.onSelected("only one");
    }
}

So here, when isMulti is true, you have onSelected taking an array, and a single string otherwise.

TypeScript Playground

The TypeScript Playground should be correct, but I am finding that at least in a different environment, the else is not type narrowing correctly. I have found that changing the check to be exact, if (props.isMulti === true) then the compiler does resolve the else condition to narrow to onSelected(option: string) correctly. Not a perfect soliton, but could perhaps be done instead with an enum instead of boolean.

like image 200
crashmstr Avatar answered Sep 07 '25 20:09

crashmstr