Generics in unions

Answer:

We have a discriminated union which accepts an argument of itself:

type TextInput<Props extends Inputs<Props>> = {
    type: 'text',
    value?: string,
    options?: undefined
}

type CheckboxInput<Props extends Inputs<Props>> = {
    type: 'checkbox',
    options: string[] | number[],
    value?: Props['options'] extends Array<infer T> ? T : never
}

type Events<Props extends Inputs<Props>> = { onInput?: (arg: Inputs<Props>['value']) => any }

type Inputs<Props extends Inputs<Props>> = TextInput<Props> | CheckboxInput<Props>

This works, and we can do some remarkable things, such as passing a string for a TextInput parameter value, and passing a number matching the type of options for a CheckboxInput parameter value.

We can use the union as an argument to a function like so:

type Make = <P extends Inputs<P>>(props: P) => {}

declare const create: Make

create({ type: 'text', value: '123' })  // πŸ‘
create({ type: 'text', value: 123 }) // πŸ‘ (value must be a string)
create({ type: 'checkbox', options: ['a', 'b'], value: 'a' }) // πŸ‘
create({ type: 'checkbox', options: [1, 2, 3], value: 2 }) // πŸ‘
create({ type: 'checkbox', options: ['a', 'b'], value: 2 }) // πŸ‘ (value must match options type)

However, is there any way to use the narrowed union data as the argument of a method inside the union? For example, if we add a onInput method to the union, can we ensure that the argument of that method also matches the value type?

type TextInput<Props extends Inputs<Props>> = {
    type: 'text',
    value?: string,
    options?: undefined
} & Events<Props>

type CheckboxInput<Props extends Inputs<Props>> = {
    type: 'checkbox',
    options: string[] | number[],
    value?: Props['options'] extends Array<infer T> ? T : never
} & Events<Props>

type Events<Props extends Inputs<Props>> = { onInput?: (arg: Inputs<Props>['value']) => any }

type Inputs<Props extends Inputs<Props>> = TextInput<Props> | CheckboxInput<Props>

type Make = <P extends Inputs<P>>(props: P) => {}

declare const create: Make

create({ type: 'checkbox', options: [1, 2, 3], value: 1, onInput: (arg) => arg }) // πŸ‘Ž "arg" in onInput should only be a number, matching the value type

Question: Is there any way to use the narrowed union data as the argument of a method inside the union?

No, there is currently no way to ensure that the argument of the onInput method matches the narrowed value type in the discriminated union. The TypeScript type system does not support this level of automatic type inference for method arguments based on the discriminated union’s value.

In the provided example, the onInput method accepts the argument of Inputs<Props>['value'], which is the union of all possible value types in the discriminated union. It cannot automatically narrow down the argument type based on the specific value type of the TextInput or CheckboxInput.

To enforce type safety for the onInput method, you would need to manually check the value type within the method implementation or use additional runtime checks.