Reputation: 3130
Let's say I want to use the React higher-order component pattern to transform a subset of my props before they hit my component:
type UpstreamProps<X> = {
foo?: number
bar?: string
baz: X
}
type DownstreamProps<X> = {
foo: number
bar: string
baz: X
bix: string
}
function transform<X>(props: UpstreamProps<X>): DownstreamProps<X> {
return {
...props,
foo: props.foo || 0,
bar: props.bar || '(unknown)',
bix: `${props.foo}-${props.bar}-bix`
}
}
At the widget definition, I want to use downstream (strict) props:
function Parent<X>(props: DownstreamProps<X> & {formatter: (x: X) => string}) {
return (
<>
foo = {props.foo}
bar = {props.bar}
baz = {props.formatter(props.baz)}
bix = {props.bix}
</>
)
}
At the call site, I should be able to pass in upstream (optional) props:
<WrappedParent<number>
baz={22}
formatter={(x: number) => `my number squared is ${Math.pow(x, 2)}`}
/>
This is as close as I've gotten to writing the higher-order component, but I can't get it to play well with the generics on the Parent
/WrappedParent
:
function withUpstreamProps<X, OtherProps>(
WrappedComponent: React.ComponentType<DownstreamProps<X> & OtherProps>
) {
return function({foo, bar, baz, ...otherProps}: UpstreamProps<X> & OtherProps) {
return (
<WrappedComponent
{...otherProps}
{...transform({foo, bar, baz})}
/>
)
}
}
export default withUpstreamProps(Parent) // doesn't keep generics
There are two requirements here that are difficult to reconcile:
withUpstreamProps(someRandomComponent)
should be an error. withUpstreamProps
should only accept a valid component as an argument; i.e., one that accepts a superset of DownstreamProps<X>
.WrappedParent
should be parameterized by one generic parameter; e.g. WrappedParent<number>
.How can I write and/or use my higher-order component wrapper to meet these requirements?
Upvotes: 1
Views: 753
Reputation: 89
If you want to pass the generic type to the resulting component, the function returned from the HOC should be generic and take the X
type as an argument and then pass it to WrappedComponent
.
function withUpstreamProps(
WrappedComponent: React.FunctionalComponent
) {
return function <X, OtherProps>({
foo,
bar,
baz,
...otherProps
}: UpstreamProps<X> & OtherProps) {
return (
<WrappedComponent<X> {...otherProps} {...transform({ foo, bar, baz })} />
);
};
}
The OtherProps
type should be passed too, so the WrappedParent
would recognize the formatter
function.
You should also assign a type for the formatter
function when using the resulting component (WrappedParent
)
Upvotes: 1
Reputation: 13184
Would something like that work for your case?:
type Wrapped<X, Other> = React.ComponentType<Output<X> & Other>
function withUpstreamProps<C extends Wrapped<any, any>>(
WrappedComponent: C
) {
return function<X, OtherProps>({foo, bar, baz, ...otherProps}: Input<X> & OtherProps) {
return (
<WrappedComponent
{...otherProps}
{...transform({foo, bar, baz})}
/>
)
}
}
Upvotes: 1