Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Type check fails when passing variables to component but works when passing values ​directly

I recently developed a Select component using Typescript and React. It has two modes to choose from: single-select and multiple-select.
Everything works fine in single-select mode, but in multiple-select mode, it will appear that TS cannot correctly infer the parameter type.
Here is my code:

type SelectProps<T> =
  | {
      value: T | undefined | null;
      multiple?: undefined;
      onChange?: (value: T | null) => void;
    }
  | {
      value: T[];
      multiple: true;
      onChange?: (value: T[]) => void;
    };

function Select<T>(props: SelectProps<T>) {
  return <div>...</div>;
}

const n = [1];
// Error:
// Type '{ value: number[]; multiple: true; onChange: (v: number[][]) => void; }' is not assignable to type 'IntrinsicAttributes & SelectProps<number[]>'.
//   Types of property 'value' are incompatible.
//     Type 'number[]' is not assignable to type 'number[][]'.
//       Type 'number' is not assignable to type 'number[]'.
const a = <Select value={n} multiple onChange={(v) => {}}></Select>;

// This works fine
const b = <Select value={[1]} multiple onChange={(v) => {}}></Select>;

I asked ChatGPT, and it suggested that I explicitly specify the <number> type, which did work:

const a = <Select<number> value={n} multiple onChange={(v) => {}}></Select>;

but when I asked it how to refactor the code so that TS can automatically infer the correct type, the answer it gave still had the same problem.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Is there any way to make TS’s automatic inference work without explicitly specify the <number>?

>Solution :

You can test whether T itself is an Array, instead of having an Array of T. Though note that this makes multiple become the dependent variable and value become the independent variable (i.e value decide the type, error will be shown on multiple).

type SelectProps<T> = T extends Array<any> ? | {
  value: T;
  multiple: true;
  onChange?: (value: T) => void;
} : {
  value: T | undefined | null;
  multiple?: undefined | false;
  onChange?: (value: T | null) => void;
};

If you want multiple to take the priority instead, you can write

type SelectProps<T,U> = U extends true ? | {
  value: T[];
  multiple: U;
  onChange?: (value: T[]) => void;
} : {
  value: T | undefined | null;
  multiple?: U;
  onChange?: (value: T | null) => void;
};

then multiple will decide the type, error will be shown on value

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading