Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
KasparEtter opened this issue Jun 3, 2020 · 2 comments
Assignees
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@KasparEtter
Copy link

KasparEtter commented Jun 3, 2020

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>;
}
@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jun 15, 2020
@RyanCavanaugh RyanCavanaugh added this to the Typescript 4.0.1 milestone Jun 15, 2020
@weswigham
Copy link
Member

Minimal non-JSX repro:

interface CustomProperties {
    text: string;
}

declare var x: Readonly<any> & Readonly<{ children?: {} }>;
declare var y: Readonly<CustomProperties> & Readonly<{ children?: {} }>;

y = x;

likely unintended fallout of our stricter intersection member checking - namely I know that Readonly<any> actually becomes {readonly [index: string]: any}, which behaves very differently than any.

@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Aug 31, 2020
@weswigham weswigham added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Needs Investigation This issue needs a team member to investigate its status. labels Feb 14, 2023
@weswigham
Copy link
Member

We'd need to create a special-case type for Readonly<any> that behaves more any-like than an index signature does to fix this, and, without other compelling examples, I don't see us integrating something like that in the near future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

4 participants