Skip to content

TS 3.9.3 breaks HOC around generic class-based components that worked with TS 3.8.3 #38910

Closed
@KasparEtter

Description

@KasparEtter

TypeScript Version: 3.9.3

Search Terms:

  • ComponentType
  • React generic component
  • Higher-order component (HOC)
  • Assignment of intersections
  • TS2322, "is not assignable to type"

Code

import { Component, ComponentType, createElement } from 'react';

class CustomLibraryComponent<P> extends Component<P> {
    public render(): JSX.Element {
        return <p>Hello world!</p>;
    }
}

interface CustomProperties {
    text: string;
}

const assignment: ComponentType<CustomProperties> = CustomLibraryComponent; // ERROR
tsconfig.json
{
    "compilerOptions": {
        "jsx": "react",
        "jsxFactory": "createElement",
        "moduleResolution": "node",
        "target": "ESNext",
    },
}
package.json
{
    "private": true,
    "scripts": {
        "compile": "tsc --project tsconfig.json"
    },
    "devDependencies": {
        "@types/react": "16.9.35",
        "typescript": "3.9.3"
    }
}
Additional explanations The error can be reproduced by executing npm run compile. The example is silly, of course, but it's the most compact way to demonstrate the problem. I want to be able to decorate a class-based component with a generic type parameter. (I want to use my custom library component in different places with different type instantiations, thus removing <P> from CustomLibraryComponent solves the problem described here but my actual use case requires the generic parameter, so please ignore that P is not actually used in the example above.) And I'm not interested in the assignment per se but the following, more realistic code fails due to the same reason:
function decorator(WrappedComponent: ComponentType<CustomProperties>) {
    return class HOC extends Component {
        public render(): JSX.Element {
            return <WrappedComponent text="Example" />;
        }
    };
}
const result = decorator(CustomLibraryComponent);
In my actual use case, the decorator itself is also generic but this just adds unnecessary detail and complexity here.

Expected behavior: Either TS 3.9.3 should still not complain or give me a way to fix the issue.

Actual behavior: Compiling the above code results in

error TS2322: Type 'typeof CustomLibraryComponent' is not assignable to type 'ComponentType<CustomProperties>'.
  Type 'typeof CustomLibraryComponent' is not assignable to type 'ComponentClass<CustomProperties, any>'.
    Construct signature return types 'CustomLibraryComponent<any>' and 'Component<CustomProperties, any, any>' are incompatible.
      The types of 'props' are incompatible between these types.
        Type 'Readonly<any> & Readonly<{ children?: ReactNode; }>' is not assignable to type 'Readonly<CustomProperties> & Readonly<{ children?: ReactNode; }>'.
          Property 'text' is missing in type 'Readonly<any> & Readonly<{ children?: ReactNode; }>' but required in type 'Readonly<CustomProperties>'.

const assignment: ComponentType<CustomProperties> = CustomLibraryComponent;

It's not clear to me whether this is really a problem with TypeScript, actually. The reason why I decided to report this issue here (rather than at DefinitelyTyped or simply asking on StackOverflow) is because it isn't obvious to me why TypeScript complains about Type 'Readonly<any> & Readonly<{ children?: ReactNode; }>' is not assignable to type 'Readonly<CustomProperties> & Readonly<{ children?: ReactNode; }>'. Additionally, it's clearly TypeScript that introduced the error. Using "typescript": "3.8.3", the above code works fine.

An alternative to permitting this assignment again would be to somehow tell TypeScript the type to use instead of any but I have no idea how. The following alternatives all fail with different errors:

const assignment: ComponentType<CustomProperties> = CustomLibraryComponent<CustomProperties>;
const assignment: ComponentType<CustomProperties> = CustomLibraryComponent as CustomLibraryComponent<CustomProperties>;
const assignment: ComponentType<CustomProperties> = CustomLibraryComponent as unknown as CustomLibraryComponent<CustomProperties>;

If the error could be fixed by improving the typing of React, please let me know (ideally with a suggestion how to fix it there).

Playground Link

Related Issues: I assume the error has been introduced with the pull request #37195. The only other regression I found in this regard is #38542.

Workaround: Interestingly, generic functional components still work. The above code compiles with TypeScript 3.9.3 when the CustomLibraryComponent is declared as follows:

function CustomLibraryComponent<P>(props: Readonly<P>): JSX.Element {
    return <p>Hello world!</p>;
}

Metadata

Metadata

Assignees

Labels

Design LimitationConstraints of the existing architecture prevent this from being fixedRescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions