-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improves the types of createHigherOrderComponent and its usages #41138
Conversation
Size Change: +10 B (0%) Total Size: 1.24 MB
ℹ️ View Unchanged
|
What I discovered in #40984 is that I think we might want to avoid adding a For the prop-injecting HOCs with default overrides we shouldn't demand that its own props can't be passed in; for the prop-injecting HOCs whose props are purely for the HOC we shouldn't demand that the inner component must receive them. This all makes me think we should either leave out any/all types for HOCs or try and find a way to deprecate const WithName = <C extends ComponentType>(component: C, name: string, …): C => {
…
return component;
} To me this approach lets people focus on the types of the individual HOCs which I think is the only reasonable place given that they all might have different (type-able) restrictions, and this helper function we have doesn't have to act like it's doing more than it is, which is mutate the thing passed into it by adding a single property. |
As I wrote in #40984 (comment), these are not issues with the types themselves, but with how TypeScript doesn't allow extra props in object literals or in "JSX literals." When there is a wrapped component: const WrappedFoo = withInstanceId( Foo ); Then the return <WrappedFoo instanceId={ null } /> is still legal from a type compatibility point of view. The reason why TypeScript rejects this is that it has a special rule that "literal" values can't have extra fields. Writing the same as: return <WrappedFoo { ...{ instanceId: null } } /> is OK because the "literal" syntax has been removed. It's also true that if I wanted to write a HOC that injects a value only when not supplied, typically like: const withSite = ( Inner ) => ( { siteId, ...props } ) => {
const injectedSiteId = useSelector( getSelectedSite );
return <Inner { ...props } siteId={ siteId ?? injectedSiteId } />;
} then the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving while leaving a note about @ts-ignore
; it would be good to address that but this seems like a step in the right direction regardless.
|
||
return <OriginalComponent { ...props } />; | ||
return ( | ||
// @ts-ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we see if we can get rid of this or replace it with ts-expect-error
(or whatever the expect-error syntax is?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that ts-ignore
is more precise here because there is not really any type error: the OriginalComponent
is always getting correct props whatever its type (the C
type variable) is. It's just TypeScript is not able to see that. Because it involves some reasoning about C
that it cannot do. In future, it might be able to and then stops reporting an error.
@dmsnell @sarayourfriend This is my attempt at correcting the types of
createHigherOrderComponent
and its applications, especially HOCs that inject props.The
createHigherOrderComponent
type has two type parameters, the type of the input and output components. They both need to be a subtype ofComponentType<any>
, so that we can read and write their.displayName
fields. There are no further constraints an in practice, the types are always easily inferred from the type ofmapComponent
.This is a blueprint for creating a prop-injecting HOC, with some new helper types:
The
C extends WithInjectedProps< C, FooProps >
constraint checks thatC
, type of the wrapped component, supports all the props that are going to be injected. IfBar
doesn't support afoo
prop, thenwithFoo( Bar )
will be a type error.The result of
withFoo( C )
has typeWithoutInjectedProps< C, FooProps >
, i.e., all the props ofC
with injected props removed.The
@ts-ignore
I had to do on the<Inner>
JSX, that's a mysterious behavior that's probably a bug either in TypeScript itself or in@types/react
. This is a minimalistic example for it that you can paste into TS playground:There will be a type error reported:
even though it's perfectly fine to pass a
foo
prop toComp
. Maybe I'll be filing a bug.Let me know what you think.