Typescript: React .tsx with Union Type Props reports error. But works in pure TS

Created on 27 Jul 2018  路  3Comments  路  Source: microsoft/TypeScript


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

Bug

Most helpful comment

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.

All 3 comments

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.
Was this page helpful?
0 / 5 - 0 ratings