TypeScript: HOC returned component props can not differ from HOC generic props
TypeScript Version: 3.3.0-dev.20181208
Search Terms:
- HOC
- react
- higher order component
Code
import * as React from 'react';
export interface HOCProps {
foo: number;
}
/** Remove props, that have been prefilled by the HOC */
type WithoutPrefilled<T extends HOCProps> = Pick<T, Exclude<keyof T, 'foo'>>;
function withFoo<P extends HOCProps>(WrappedComponent: React.ComponentType<P>) {
return class SomeHOC extends React.Component<WithoutPrefilled<P>> {
public render(): 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 https://github.com/Microsoft/TypeScript/issues/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.
Playground Link:
Related Issues: https://github.com/Microsoft/TypeScript/issues/28720
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 78
- Comments: 23 (3 by maintainers)
Commits related to this issue
- port hoc.js to typescript this turned out to be really messy. first of all, typing HOCs is not really easy. but once I started to get going with it, I hit a TypeScript 3.2 bug that prevents the HOC... — committed to nurpax/petmate by nurpax 6 years ago
- Fix hoc typing See: https://github.com/Microsoft/TypeScript/issues/28938#issuecomment-450636046 — committed to nusmodifications/nusmods by li-kai 5 years ago
- Migrate www to typescript (#1537) * Rename all js, jsx to ts, tsx * Add typescript configuration * Add naive flow -> typescript convertor * Add configuration to fix typescript code * Conv... — committed to nusmodifications/nusmods by li-kai 5 years ago
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.@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.Ok here is another take from @dex157:
This way we don’t even have to use
Omitand type inference works correctly:Here is a TypeScript playground to try it out.
Basically the difference in @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
I’m kind of reviving this thread but casting is not a proper solution, it’s highly unsafe. Here’s a sample:
This sample is totally valid even
Componentdoes not receive eitheronChangeandnamewhich are required props. We ship a massive bug due to this in our product today because of this. This really needs to be fixed, casting is such a bad idea and I feel like I have to use it too many times. If someone has a better workaround solution, I would be happy about itI’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.
I think that this issue can be closed, since the implicit types of the spread operator were changed.
This is a fully working example you can use with TS 3.5+ (or for 3.2+ use
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>)Of course, if you need to shadow only one property explicitly, you don’t need the
ShadowInjectedtype and useOmit<HOCProps, "yourProperty">instead.Had to use
<WrappedComponent {...this.props as any} inject={injected}/>to temporarily bypass the HOC props merge.@lvkins the idea here is to force
WrappedComponentto extend your HOC injected propsmy case example
I’m seeing the same thing as the original poster, but from version 3.1.6 to 3.2.2
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 @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.
@weswigham Is this the same as https://github.com/Microsoft/TypeScript/issues/28748?