You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
HOC returned component props can not differ from HOC generic props #28938
import*asReactfrom'react';exportinterfaceHOCProps{foo: number;}/** Remove props, that have been prefilled by the HOC */typeWithoutPrefilled<TextendsHOCProps>=Pick<T,Exclude<keyofT,'foo'>>;functionwithFoo<PextendsHOCProps>(WrappedComponent: React.ComponentType<P>){returnclassSomeHOCextendsReact.Component<WithoutPrefilled<P>>{publicrender(): JSX.Element{return<WrappedComponent{...this.props}foo={0}/>;}};}
Expected behavior:
No error, like with every version below 3.2.0.
Actual behavior:
Throws an error highlighting the WrappedComponent in the render method.
[ts]
Type 'Readonly<{ children?: ReactNode; }> & Readonly<Pick<P, Exclude<keyof P, "foo">>> & { foo: number; }' is not assignable to type 'IntrinsicAttributes & P & { children?: ReactNode; }'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly<Pick<P, Exclude<keyof P, "foo">>> & { foo: number; }' is not assignable to type 'P'. [2322]
Additional Information
This is pretty much the same sample example used in #28720, but with the difference, that the props of the returned component differ from the generic.
Basically the HOC prefills the foo property for the WrappedComponent. Since the spreaded props are overriden by foo, I don’t want foo to be a valid property for the HOC. This does not seem to be possible anymore.
I believe I'm having the exact same problem on TypeScript 3.2.2, and I'd like to share my use case to show another way this is affecting HOCs in React. In my case I have a HOC that grabs WrappedComponent's children to render it in a different way, forwarding all props but children to WrappedComponent, resulting in the exact same error Henning Pohlmeyer (@hpohlmeyer) mentioned.
Here's a simplified (React Native) code:
import*asReactfrom'react';import{ViewProps}from'react-native';exportconstwithXBehavior=<PextendsViewProps>(WrappedComponent: React.ComponentType<P>,): React.ComponentType<P>=>(props)=>{const{ children, ...otherProps}=props;return(<WrappedComponent{...otherProps}>/* ERROR HERE */<React.Fragment><View><Text>exampleofHOCstuffhere</Text></View>{children}</React.Fragment></WrappedComponent>);};
Error: Type '{ children: Element; }' is not assignable to type 'P'. [2322]
This was working fine on 3.0.1 and is now giving me this error on 3.2.2 (haven't tested on 3.1).
In my real world case I need <P extends ViewProps> to have the ability to use onLayout:
If I forward all HOC props to WrappedComponent the error is gone, but that would prevent HOCs to change behavior of WrappedComponents.
import*asReactfrom'react';import{Text,View,ViewProps}from'react-native';exportconstwithXBehavior=<PextendsViewProps>(WrappedComponent: React.ComponentType<P>,): React.ComponentType<P>=>(props)=>{const{ children }=props;return(<WrappedComponent{...props}><React.Fragment><View><Text>exampleofHOCstuffhere</Text></View>{children}</React.Fragment></WrappedComponent>);};
Anders Hejlsberg (@ahejlsberg) The change is that we no longer erase generics in JSX (so we actually check these calls now), and roughly that Pick<P, Exclude<keyof P, "foo">> & { foo: P["foo"] } doesn't recombine to (or get recognized as assignable to) P. It's an unfortunate interaction with generic rest/spread, and the error we output is bad, too.
Reacted by Adam Rutkowski, Henning Pohlmeyer, Ferdy Budhidharma, Amit Dahan, Philip Jackson, Massimiliano Mirra, Linjie Ding, sashless, Dr. Lukas Wollmann, Frank Wallis and 6 more
Just for the sake of completeness: In some cases we do not remove the prop from the returned component, but make it optional instead. This also fails for the same reason:
import*asReactfrom'react';exportinterfaceHOCProps{foo: number;}/** From T make every property K optional */typePartialize<T,KextendskeyofT>=Pick<T,Exclude<keyofT,K>>&Partial<Pick<T,K>>;/** Remove props, that have been prefilled by the HOC */typeOptionalPrefilled<TextendsHOCProps>=Partialize<T,keyofHOCProps>;exportdefaultfunction<PextendsHOCProps>(WrappedComponent: React.ComponentType<P>){returnclassSomeHOCextendsReact.Component<OptionalPrefilled<P>>{publicrender(): JSX.Element{return<WrappedComponentfoo={0}{...this.props}/>;}};}
Error:
[ts]
Type 'Readonly<{ children?: ReactNode; }> & Readonly<Partialize<P, "foo">> & { foo: number; }' is not assignable to type 'IntrinsicAttributes & P & { children?: ReactNode; }'.
Type 'Readonly<{ children?: ReactNode; }> & Readonly<Partialize<P, "foo">> & { foo: number; }' is not assignable to type 'P'. [2322]
So basically it’s the same. I just wanted to provide another example where TypeScript does not recognize that the type is assignable to T. This could be harder to solve then the original one though.
Starting with 3.2 the behaviour of the spread operator for generics has changed. Apparently the type of props gets erased as a negative side effect, but you can work around that by casting it back to P using {...props as P} when spreading back into the wrapped component.
Reacted by Juan Torello, Tom Shea, Erik Živković, bySabi Files, Yann Pringault, Artem Trytiak, mr-sanders, James Lakin, Matt Davis, benknab and 26 more
Reacted by Tom Shea, Erik Živković, Yann Pringault, Simon Archer, Jesse Pinho, benknab, Jakob Rames, Stanislav Iliev, sashless, Dmitry Druganov and 8 more
Reacted by Dr. Lukas Wollmann, Brian Kent, dyahadilatvlk, Chris Domigan, まつまつ and 流浪大法师
Apparently the type of props gets erased as a negative side effect, but you can work around that by casting it back to P using {...props as P} when spreading back into the wrapped component.
Is there a bug somewhere where we can track this specific problem?
Anders Hejlsberg (@ahejlsberg) The change is that we no longer erase generics in JSX (so we actually check these calls now), and roughly that Pick<P, Exclude<keyof P, "foo">> & { foo: P["foo"] } doesn't recombine to (or get recognized as assignable to) P. It's an unfortunate interaction with generic rest/spread, and the error we output is bad, too.
gabriellsh not sure why do you need to get the rest as you are overriding the foo prop in my example. React will accept the last value of the prop that was passed, here is a codesandbox.
Type 'Omit<P, "foo"> & { foo: string; }' is not assignable to type 'P'.
'Omit<P, "foo"> & { foo: string; }' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint 'Foo'.ts(2322)
exportfunctionwithBaseProviders<Pextends{}>(WrappedComponent: React.ComponentType<P>,){constWithBaseProviders=({ a, b, c, d, e, ...props}: P&BaseProvidersProps)=>(<BaseProvidersa={a}b={b}c={c}d={d}e={e}><WrappedComponent{...props}/></BaseProviders>);WithBaseProviders.displayName=`BaseProviders(${WrappedComponent.displayName||WrappedComponent.name||"Component"})`;returnWithBaseProviders;}
That raised:
Type 'Omit<P & BaseProvidersProps, "a" | "b" | "c" | "d" | "e">' is not assignable to type 'IntrinsicAttributes & P'.
Type 'Omit<P & BaseProvidersProps, "a" | "b" | "c" | "d" | "e">' is not assignable to type 'P'.
'Omit<P & BaseProvidersProps, "a" | "b" | "c" | "d" | "e">' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint '{}'.ts(2322)
To extend the generic type and then explicitly omit on the wrapped:
exportfunctionwithBaseProviders<PextendsBaseProvidersProps>(WrappedComponent: React.ComponentType<Omit<P,keyofBaseProvidersProps>>,){constWithBaseProviders=({ a, b, c, d, e, ...props}: P)=>(<BaseProvidersa={a}b={b}c={c}d={d}e={e}><WrappedComponent{...props}/></BaseProviders>);WithBaseProviders.displayName=`BaseProviders(${WrappedComponent.displayName||WrappedComponent.name||"Component"})`;returnWithBaseProviders;}
This error is correct. P extends HOCProps does not mean that P is exactly HOCProps; it can require a narrower foo type. For example, P could be { foo: 1; label: string }. The HOC supplies foo={0}, so the props assembled from WithoutPrefilled<P> and { foo: number } are not safe to pass to every possible React.ComponentType<P>.
Activity
flsilva commentedon Dec 10, 2018
I believe I'm having the exact same problem on TypeScript 3.2.2, and I'd like to share my use case to show another way this is affecting HOCs in React. In my case I have a HOC that grabs
WrappedComponent'schildrento render it in a different way, forwarding all props but children toWrappedComponent, resulting in the exact same error Henning Pohlmeyer (@hpohlmeyer) mentioned.Here's a simplified (React Native) code:
Error:
Type '{ children: Element; }' is not assignable to type 'P'. [2322]This was working fine on 3.0.1 and is now giving me this error on 3.2.2 (haven't tested on 3.1).
In my real world case I need
<P extends ViewProps>to have the ability to useonLayout:<WrappedComponent {...otherProps} onLayout={this.myFunction} />If I forward all HOC props to WrappedComponent the error is gone, but that would prevent HOCs to change behavior of WrappedComponents.
No errors in the above code.
SpiderSkippy commentedon Dec 10, 2018
I'm seeing the same thing as the original poster, but from version 3.1.6 to 3.2.2
weswigham commentedon Dec 10, 2018
Anders Hejlsberg (@ahejlsberg) The change is that we no longer erase generics in JSX (so we actually check these calls now), and roughly that
Pick<P, Exclude<keyof P, "foo">> & { foo: P["foo"] }doesn't recombine to (or get recognized as assignable to)P. It's an unfortunate interaction with generic rest/spread, and the error we output is bad, too.port hoc.js to typescript
hpohlmeyer commentedon Dec 11, 2018
Just for the sake of completeness: In some cases we do not remove the prop from the returned component, but make it optional instead. This also fails for the same reason:
Error:
So basically it’s the same. I just wanted to provide another example where TypeScript does not recognize that the type is assignable to T. This could be harder to solve then the original one though.
sveyret commentedon Dec 15, 2018
It looks like the same problem as in #28884. It fails even if no spread rest.
ChristianIvicevic commentedon Dec 31, 2018
Starting with 3.2 the behaviour of the spread operator for generics has changed. Apparently the type of
propsgets erased as a negative side effect, but you can work around that by casting it back toPusing{...props as P}when spreading back into the wrapped component.OliverJAsh commentedon Jan 21, 2019
Christian Ivicevic (@ChristianIvicevic)
Is there a bug somewhere where we can track this specific problem?
OliverJAsh commentedon Jan 22, 2019
Wesley Wigham (@weswigham) Is this the same as #28748?
aprilandjan commentedon Jan 25, 2019
Had to use
<WrappedComponent {...this.props as any} inject={injected}/>to temporarily bypass the HOC props merge.Fix hoc typing
Migrate www to typescript (#1537)
4 remaining items
satansdeer commentedon Mar 3, 2021
I've tried to do this to avoid type assertion:
This way the wrapped component can't receive the injected props and TypeScript does not complain.
Here is a TypeScript playground with an example of how this HOC can be used.
gabriellsh commentedon Mar 4, 2021
Maksim Ivanov (@satansdeer) but you're not getting the rest props, it should be
return ({somekey, ...props}) =>....satansdeer commentedon Mar 4, 2021
gabriellsh not sure why do you need to get the
restas you are overriding thefooprop in my example. React will accept the last value of the prop that was passed, here is a codesandbox.satansdeer commentedon Mar 4, 2021
Ok here is another take from Artem Samofalov (@dex157):
This way we don't even have to use
Omitand type inference works correctly:Here is a TypeScript playground to try it out.
coalaroot commentedon Apr 8, 2021
Basically the difference in Maksim Ivanov (@satansdeer) answer is to use type (which is not stated there explicitly) instead of interfaces
This will not work
And this will
The difference is in ButtonProps
satansdeer commentedon Apr 11, 2021
Jakub Królak (@coalaroot) great catch. Yes, using the
extendssyntax is not the same as having an intersection type.Omelyan commentedon Apr 27, 2023
will... how about generics in FooProps? :))
coalaroot commentedon Apr 27, 2023
Can do but why though?
coalaroot commentedon Apr 27, 2023
Sorry man, don't have the time to figure it out :(
hayata-suenaga commentedon Jul 6, 2023
This is the simplest reproducible sample code
This raises the following error:
lifeiscontent commentedon Sep 4, 2024
Hayata Suenaga (@hayata-suenaga) weird thing is, this works:
keropodium commentedon May 23, 2025
In my case I had to go from this:
That raised:
To extend the generic type and then explicitly omit on the wrapped:
RyanCavanaugh commentedon Sep 1, 2026
This error is correct.
P extends HOCPropsdoes not mean thatPis exactlyHOCProps; it can require a narrowerfootype. For example,Pcould be{ foo: 1; label: string }. The HOC suppliesfoo={0}, so the props assembled fromWithoutPrefilled<P>and{ foo: number }are not safe to pass to every possibleReact.ComponentType<P>.This is the distinction described in Why can't I narrow or assign to a generic type parameter the way I can with its constraint?. TypeScript 3.2 correctly began checking the generic JSX call instead of erasing its type parameter.