I'm writing a React higher-order component (HOC) with TypeScript. The HOC should accept one more prop than the wrapped component, so I wrote this:
type HocProps {
// Contains the prop my HOC needs
thingy: number
}
type Component<P> = React.ComponentClass<P> | React.StatelessComponent<P>
interface ComponentDecorator<TChildProps> {
(component: Component<TChildProps>): Component<HocProps & TChildProps>;
}
const hoc = function<TChildProps>(): (component: Component<TChildProps>) => Component<HocProps & TChildProps) {
return (Child: Component<TChildProps>) => {
class MyHOC extends React.Component<HocProps & TChildProps, void> {
// Implementation skipped for brevity
}
return MyHOC;
}
}
export default hoc;
In other words, hoc
is a function that yields the actual HOC. This HOC is (I believe) a function that accepts a Component
. Since I don't know in advance what the wrapped component will be, I'm using a generic type TChildProps
to define the shape of the props of the wrapped component. The function also returns a Component
. The returned component accepts props for the wrapped component (again, typed using the generic TChildProps
) and some props it needs for itself (type HocProps
). When using the returned component, all of the props (both HocProps
and the props for the wrapped Component
) should be supplied.
Now, when I attempt to use my HOC, I do the following:
// outside parent component
const WrappedChildComponent = hoc()(ChildComponent);
// inside parent component
render() {
return <WrappedChild
thingy={ 42 }
// Prop `foo` required by ChildComponent
foo={ 'bar' } />
}
But I get a TypeScript error:
TS2339: Property 'foo' does not exist on type 'IntrinsicAttributes & HocProps & {} & { children? ReactNode; }'
It seems to me TypeScript is not replacing TChildProps
with the shape the of the props needed for ChildComponent
. How can I make TypeScript do that?
Your problem is that
hoc
is generic, but takes no parameters—there’s no data to infer anything from. That forces you to explicitly specify the type forhoc
when you call it. Even if you dohoc()(Foo)
, Typescript has to evaluatehoc()
first—and there’s zero information to go on at that point. Hence needinghoc<FooProps>()(Foo)
.If the body of
hoc
doesn’t require the type and can be flexible on that point, you could makehoc
not be generic, but instead return a generic function (the higher-order component).That is, instead of
you could instead have
(note the placement of
<TChildProps>
)Then when you call
hoc()
, you get a generic function, and when you callhoc()(Foo)
, it will be inferred to be equivalent tohoc()<FooProps>(Foo)
.This does not help you if
hoc
itself needs to do something with the type parameter—if it does, you are saying “give me a function that will only accept arguments that are components with this type as a property,” and you need to give it that type then. Whether or not this helps depends on what the actualhoc
function is doing, which isn’t demonstrated in your question.I assume that your actual use-case is doing something in
hoc
, rather than just immediately returning the higher-order component function. Certainly, if not, you should just remove the layer of indirection and have there just be the higher-order component. Even if you are doing things insidehoc
, I would strongly suggest that there is a pretty good chance you are better off just not doing that, moving that functionality to within the actual higher-order component, and eliminating the indirection. Your approach only makes sense if you are callinghoc
once or a few times, but then using the resultinghoc()
function(s) many, many times with different parameters—and even then, this would be an optimization that may well be premature.If what you're asking for is if it's possible to define a HOC that can add a new prop, let's say "thingy", to a component without modifying that component's props definition to include "thingy" I think that's impossible.
That's because at some point in the code you'll end up with:
And that will always throw an error if
WrappedComponent
does not includethingy
in its props definition. The child has to know what it receives. Off hand, I can't think of a reason for passing a prop to a component that doesn't know about to it anyway. You wouldn't be able to reference that prop in the child component without an error.I think the trick is to define the HOC as a generic around the props of the child and then to just include your prop
thingy
or whatever in that child's interface explicitly.Now of course this example HOC doesn't really do anything. Really HOC's should be doing some sort of logic to provide a value for the child prop. Like for example maybe you have a component that displays some generic data that gets updated repeatedly. You could have different ways it gets updated and create HOC's to separate that logic out.
You have a component:
And then an example HOC that just updates the child component on a fixed interval using
setInterval
might be:Then we could create a component that updates our child once every second like this:
I found one way to make it work: by invoking the
hoc
with the type argument supplied, like so:But I don't really like it. It requires me to export the props of the child (which I'd rather not do) and I have the feeling I'm telling TypeScript something it should be able to infer.