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; // 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 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>;
}