Skip to content

React .tsx with Union Type Props reports error. But works in pure TS #26004

Closed

Description

@benneq

TypeScript Version: 2.9.2
(also tried typescript@3.1.0-dev.20180727 )

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

Activity

  1. mattmccutchen commented on Jul 27, 2018

    @mattmccutchen
    Contributor

    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?

  2. pelotom commented on Aug 14, 2018

    @pelotom

    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.
  3. pelotom commented on Aug 14, 2018

    @pelotom

    Also here's a case where wrapping the props in a JSX spread doesn't help:

    import * as React from 'react';
    
    interface ExampleProps<A> {
      initial: A
      transition(x: A): A
    }
    
    declare const Example: {
      (props: { string: true } & ExampleProps<string>): JSX.Element;
      <A>(props: ExampleProps<A>): JSX.Element;
    }
    
    <Example initial={3} transition={x => x + 1} />; // x: any
    <Example {...{initial: 3, transition: x => x + 1}} />; // x: any
    Example({ initial: 3, transition: x => x + 1 }); // x: number
  4. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions