Full HOC Example
This is an HOC example for you to copy and paste. If certain pieces don't make sense for you, head to the React HOC Docs intro to get a detailed walkthrough via a complete translation of the React docs in TypeScript.
Sometimes you want a simple way to inject props from somewhere else (either a global store or a provider) and don't want to continually pass down the props for it. Context is great for it, but then the values from the context can only be used in your render
function. A HoC will provide these values as props.
The injected props
interface WithThemeProps {
primaryColor: string;
}
Usage in the component
The goal is to have the props available on the interface for the component, but subtracted out for the consumers of the component when wrapped in the HoC.
interface Props extends WithThemeProps {
children?: React.ReactNode;
}
class MyButton extends React.Component<Props> {
public render() {
// Render an the element using the theme and other props.
}
private someInternalMethod() {
// The theme values are also available as props here.
}
}
export default withTheme(MyButton);
Consuming the Component
Now when consuming the component you can omit the primaryColor
prop or override the one provided through context.
<MyButton>Hello button</MyButton> // Valid
<MyButton primaryColor="#333">Hello Button</MyButton> // Also valid
Declaring the HoC
The actual HoC.
export function withTheme<T extends WithThemeProps = WithThemeProps>(
WrappedComponent: React.ComponentType<T>
) {
// Try to create a nice displayName for React Dev Tools.
const displayName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
// Creating the inner component. The calculated Props type here is the where the magic happens.
const ComponentWithTheme = (props: Omit<T, keyof WithThemeProps>) => {
// Fetch the props you want to inject. This could be done with context instead.
const themeProps = useTheme();
// props comes afterwards so the can override the default ones.
return <WrappedComponent {...themeProps} {...(props as T)} />;
};
ComponentWithTheme.displayName = `withTheme(${displayName})`;
return ComponentWithTheme;
}
Note that the {...(props as T)}
assertion is needed because of a current bug in TS 3.2 https://github.com/Microsoft/TypeScript/issues/28938#issuecomment-450636046
Here is a more advanced example of a dynamic higher order component that bases some of its parameters on the props of the component being passed in:
// inject static values to a component so that they're always provided
export function inject<TProps, TInjectedKeys extends keyof TProps>(
Component: React.JSXElementConstructor<TProps>,
injector: Pick<TProps, TInjectedKeys>
) {
return function Injected(props: Omit<TProps, TInjectedKeys>) {
return <Component {...(props as TProps)} {...injector} />;
};
}
Using forwardRef
For "true" reusability you should also consider exposing a ref for your HOC. You can use React.forwardRef<Ref, Props>
as documented in the basic cheatsheet, but we are interested in more real world examples. Here is a nice example in practice from @OliverJAsh (note - it still has some rough edges, we need help to test this out/document this).
Supporting defaultProps
of Wrapped Component
If this is something you need, please see the stale discussion we had and comment with your requirements. We will pick this up again if needed.