-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
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; // ERRORtsconfig.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 executingnpm 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).
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>;
}