Writing a React higher-order component with TypeScript
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:
render() {
return (
<WrappedComponent thingy={this.props.thingy} {...this.props}/>
);
}
And that will always throw an error if WrappedComponent
does not include thingy
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.
interface HocProps {
// Contains the prop my HOC needs
thingy: number;
}
const hoc = function<P extends HocProps>(
WrappedComponent: new () => React.Component<P, any>
) {
return class MyHOC extends React.Component<P, any> {
render() {
return (
<WrappedComponent {...this.props}/>
);
}
}
}
export default hoc;
// Example child class
// Need to make sure the Child class includes 'thingy' in its props definition or
// this will throw an error below where we assign `const Child = hoc(ChildClass)`
interface ChildClassProps {
thingy: number;
}
class ChildClass extends React.Component<ChildClassProps, void> {
render() {
return (
<h1>{this.props.thingy}</h1>
);
}
}
const Child = hoc(ChildClass);
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:
interface ChildComponentProps {
lastUpdated: number;
data: any;
}
class ChildComponent extends React.Component<ChildComponentProps, void> {
render() {
return (
<div>
<h1>{this.props.lastUpdated}</h1>
<p>{JSON.stringify(this.props.data)}</p>
</div>
);
}
}
And then an example HOC that just updates the child component on a fixed interval using setInterval
might be:
interface AutoUpdateProps {
lastUpdated: number;
}
export function AutoUpdate<P extends AutoUpdateProps>(
WrappedComponent: new () => React.Component<P, any>,
updateInterval: number
) {
return class extends React.Component<P, any> {
autoUpdateIntervalId: number;
lastUpdated: number;
componentWillMount() {
this.lastUpdated = 0;
this.autoUpdateIntervalId = setInterval(() => {
this.lastUpdated = performance.now();
this.forceUpdate();
}, updateInterval);
}
componentWillUnMount() {
clearInterval(this.autoUpdateIntervalId);
}
render() {
return (
<WrappedComponent lastUpdated={this.lastUpdated} {...this.props}/>
);
}
}
}
Then we could create a component that updates our child once every second like this:
const Child = AutoUpdate(ChildComponent, 1000);
A bit late to the party. I like to use the Omit TypeScript utility type to solve this issue. Link to the documentation: https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk
import React, {ComponentType} from 'react';
export interface AdditionalProps {
additionalProp: string;
}
export function hoc<P extends AdditionalProps>(WrappedComponent: ComponentType<P>) : ComponentType<Omit<P, 'additionalProp'>> {
const additionalProp = //...
return props => (
<WrappedComponent
additionalProp={additionalProp}
{...props as any}
/>
);
}