TypeScript Version: 2.9.2
(also tried [email protected] )
Search Terms: React Tsx Union Generics
Code
interface PS {
multi: false
value: string | undefined
onChange: (selection: string | undefined) => void
}
interface PM {
multi: true
value: string[]
onChange: (selection: string[]) => void
}
export function ComponentWithUnion(props: PM | PS) {
return "foo";
}
// Usage with React tsx
export function HereIsTheError() {
return (
<ComponentWithUnion
multi={false}
value={'s'}
onChange={val => console.log(val)} // <- this throws an error
/>
);
}
// Usage with pure TypeScript
ComponentWithUnion({
multi: false,
value: 's',
onChange: val => console.log(val) // <- this works fine
})
Expected behavior:
Both should infer the correct type and don't throw an error.
Actual behavior:
Throw's the following error:
Parameter 'val' implicitly has an 'any' type
Playground Link:
https://codesandbox.io/s/w07omnr85k
In file index.ts at line 25 you can see that val is of type any. But in line 34 it correctly displays type string.
Related Issues:
None
Looks like the problem is that each JSX attribute is contextually typed independently. If you use a spread attribute, it does work:
export function HereIsTheError() {
return (
<ComponentWithUnion {...{
multi: false,
value: "s",
onChange: val => console.log(val)
}}/>
);
}
Could the compiler just treat all the JSX attributes as if they were written in a single object literal?
This affects overloads as well as unions:
import * as React from 'react';
declare function Case1(arg:
| { tag: 'foo'; f(n: number): void }
| { tag: 'bar'; f(s: string): void }
): JSX.Element;
declare function Case2(x: { tag: 'foo'; f(n: number): void }): JSX.Element;
declare function Case2(x: { tag: 'bar'; f(s: string): void }): JSX.Element;
Case1({ tag: 'foo', f: n => {} });
<Case1 tag='foo' f={n => {}} />; // Parameter 'n' implicitly has an 'any' type.
Case2({ tag: 'foo', f: n => {} });
<Case2 tag='foo' f={n => {}} />; // Parameter 'n' implicitly has an 'any' type.
Also here's a case where wrapping the props in a JSX spread doesn't help:
```ts
import * as React from 'react';
interface ExampleProps {
initial: A
transition(x: A): A
}
declare const Example: {
(props: { string: true } & ExampleProps
(props: ExampleProps): JSX.Element;
}
Example({ initial: 3, transition: x => x + 1 }); // x: number
````
Most helpful comment
This affects overloads as well as unions: