Skip to content

Ban generic components in TSX #17949

@markboyall

Description

@markboyall

Kindly consider the following Typescript which reproduces the issue without TSX (because eww JSX/TSX).

interface TestProps<T> {
    item: T;
    comparator: { (original:T, updated: T): boolean };
}

class TestComponent<T> extends React.Component<{}, { switch: boolean }> {
    shouldComponentUpdate(nextProps: TestProps<T>) {
        return nextProps.comparator(this.props.item, nextProps.item);
    }
    render() {
        return null;
    }
}

const Test = <T>(props: TestProps<T>) => {
    return React.createElement(TestComponent, props);
};

class HelloComponent extends React.Component<{}, {}> {  
  render() {
    const self = this;
    const button = React.createElement("button", { onClick: () => self.setState({ switch: true })}, "Switch");
    if (!this.state || !this.state.switch) {
        return React.createElement("div", null,
           button,
           Test<int>({item: 1, comparator: (lhs, rhs) => lhs != rhs })
        );
    }
    return React.createElement("div", 
        null,
        button,
        Test<{ x: { x: number } }>({ item: { x: { x: 1 } }, comparator: (lhs, rhs) => lhs.x.x != rhs.x.x }),
        "switched");
  }
}

const Hello = React.createFactory(HelloComponent);

ReactDOM.render(
  Hello(),
  document.getElementById('container')
);

Expected outcome: compiler error or warning (or execution with no error).
Actual outcome: When the switch button is pressed, a runtime error results.

This is, of course, due to the type erasure involved- React has no idea that these are two separate component types.

I originally did not intend to file this issue because I have no idea what can actually be done about this whilst still preserving all the JS interop. I ultimately feel like TS needs two kinds of classes- the bad kind for JS interop, and a better kind which e.g. no type erasure, permits reflection, etc etc.

However, since TSX is a completely compiler special case with only one use case, I think it should be possible to simply ban the use of generic components here as they are unsafe. It may not be possible to catch all usages of e.g. aliases to generic classes but I would hope that some effort could be made.

Perhaps a more general solution would involve some kind of attribute which can enforce this on arbitrary function arguments?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraints

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions