@types/xxxx package and had problems.Definitions by: in index.d.ts) so they can respond.I'd like to create a HoC whose input is limited to React.ComponentClass, not React.ComponentType as most blog posts and tutorials suggest, so that I can set a ref to the input (wrapped) component.
I've created a repository to reproduce an issue: https://github.com/nodaguti/typescript-component-class-hoc, so please go and see the repo.
The following is a copy of its description:
Limiting the type of a wrapped component from React.ComponentType to React.ComponentClass causes an interesting error in typing resolution of the wrapped component in render().
While there is no issue if I type the wrapped component with React.ComponentType or React.StatelessComponent, TypeScript starts to complain when I switch the type to React.ComponentClass so that only a class-based component would be acceptable as the wrapped component, saying the following error:
$ tsc --project ./tsconfig.json
index.tsx:55:11 - error TS2326: Types of property 'injectedA' are incompatible.
Type '(Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps & ExternalProps> & { injectedA: 0; ...' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<Component<OriginalProps & InjectedProps, Componen...'.
Type '0' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<Component<OriginalProps & InjectedProps, Componen...'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps & ExternalProps> & { injectedA: 0; i...' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<OriginalProps & InjectedProps, Component...'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps & ExternalProps> & { injectedA: 0; i...' is not assignable to type 'Readonly<OriginalProps & InjectedProps>'.
55 injectedA={0}
~~~~~~~~~~~~~
index.tsx:56:11 - error TS2326: Types of property 'injectedB' are incompatible.
Type '(Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps & ExternalProps> & { injectedA: 0; ...' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<Component<OriginalProps & InjectedProps, Componen...'.
Type '"injected"' is not assignable to type '(IntrinsicAttributes & IntrinsicClassAttributes<Component<OriginalProps & InjectedProps, Componen...'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps & ExternalProps> & { injectedA: 0; i...' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<OriginalProps & InjectedProps, Component...'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly<OriginalProps & ExternalProps> & { injectedA: 0; i...' is not assignable to type 'Readonly<OriginalProps & InjectedProps>'.
56 injectedB="injected"
~~~~~~~~~~~~~~~~~~~~
which seems that TypeScript mistakenly believes that the type of the wrapped component's props is OriginalProps & ExternalProps, not OriginalProps & InjectedProps.
Considering the fact that typing the wrapped component with React.ComponentType and React.StatelessComponent doesn't cause the error, I believe something might go wrong with a type definition of React.ComponentClass.
yarn installyarn lintNo errors are emitted.
The above errors are emitted.
Same for me. My check it's from this commit.
The default param for state in ComponentClass cause it.
Maybe, it can be fixed with ComponentType declare ComponentClass state generic param as any.
This change also led to this issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/27545
Maybe that change should be reverted; seems it's causing problems - or would that just create different problems?
This was an issue even before "accused commit"

Ah - as you were then. BTW I just encountered a video of you @Hotell on Twitter! Zero to hero with React and TypeScript. Alas you were speaking a language I don't and so I didn't quite get the benefit.
But your dreads were lovely.
Also @nodaguti consider reading this post (everything you need to know to use TS + React ) https://levelup.gitconnected.com/ultimate-react-component-patterns-with-typescript-2-8-82990c516935
Also what you need to do if you're using ComponentClass is to use proper Original Props generic constraint
export const HoCWithComponentClass = <OriginalProps extends InjectedProps>(
Cmp: React.ComponentClass<OriginalProps>
) => {
class InjectedComponent extends React.Component<
OriginalProps & ExternalProps
> {
render() {
return <Cmp injectedA={0} injectedB="injected" {...this.props} />
}
}
@Hotell
Thank you so much for sharing your great post and giving a clear solution. Replacing <OriginalProps extends {}> with <OriginalProps extends InjectedProps> has resolved the issue like a magic!
Should I close this issue then?
For me this solution didn't work (extending interface)
@nodaguti
has resolved the issue like a magic!
While it solved partially your issue, it doesn't work in real life ( what you want is to remove the injected props or make them optional on wrapper ).
So you need to do this:
const HoCWithComponentClass = <OriginalProps extends InjectedProps>(
Cmp: React.ComponentClass<OriginalProps>
) => {
/**
* remove InjectedProps from wrapper interface and make them optional
* so following wont get any errors:
*
* ```tsx
* const EnhancedWithComponentClass = HoCWithComponentClass(TestCmp)
*
* const App = () => {
* return (
* <><EnhancedWithComponentClass externalA={'asd'} /></>
* )
* }
* ```
*/
type WrapperProps = Pick<
OriginalProps,
Exclude<keyof OriginalProps, keyof InjectedProps>
> &
Partial<InjectedProps>
class InjectedComponent extends React.Component<
WrapperProps & ExternalProps
> {
render() {
return <Cmp injectedA={0} injectedB="injected" {...this.props} />
}
}
return InjectedComponent
}
Which still throws error

Should I close this issue then?
Nope, I think it's a TypeScript generics issue. Will try to reproduce this with vanilla JS and report bug to TS.
As I've expected, this issue has nothing to do with React nor React typings :)
Here is the issue demonstrated on vanilla JS with FIX included ;) You're welcome 鉁岋笍
P.S.:
Overall I think this is correct behaviour of type checker, it cannot possibly know what OriginalProps generic type would look like when it's gonna be used by the user. For that specific reason, you need to cast it, to tell compiler: dude take it easy here ;). In the end the compiler is just protecting you from writing bad code ( in not very human readable errors unfortunately ), which is a win IMHO. just cc @DanielRosenwasser @mhegazy @sandersn to verify my hypothesis :P
Ok so this is solid and works:
// types.ts
export type WrapperProps<
OriginalProps extends InjectedProps,
InjectedProps
> = Omit<OriginalProps, keyof InjectedProps> & Partial<InjectedProps>
export type Omit<T, K extends keyof T> = T extends any
? Pick<T, Exclude<keyof T, K>>
: never
import React from 'react'
import { WrapperProps } from './types'
export const HoCWithComponentClass = <OriginalProps extends InjectedProps>(
Cmp: React.ComponentClass<OriginalProps>
) => {
// props definition exposed on final component created by this HoC
type WrapperCmpProps = WrapperProps<OriginalProps, InjectedProps> &
ExternalProps
class InjectedComponent extends React.Component<WrapperCmpProps> {
render() {
const props = {
// InjectedProps
injectedA: 12321,
injectedB: 'injected',
// OriginalProps & ExternalProps
...(this.props as object) /* need to cast to object as TS cannot spread Generics */,
} as InjectedProps & OriginalProps & ExternalProps
return <Cmp {...props} />
}
}
return InjectedComponent
}

Most helpful comment
Ah - as you were then. BTW I just encountered a video of you @Hotell on Twitter! Zero to hero with React and TypeScript. Alas you were speaking a language I don't and so I didn't quite get the benefit.
But your dreads were lovely.