I have a loader HOC
HOC:
const withLoader = <P extends object>(WrappedComponent: new () => React.Component<P, any>, loading: boolean) => {
return class WithLoading extends React.Component<P, any> {
render() {
return (
<div >
<div className={`${loading} ? 'loader' : '' "></div>
<WrappedComponent {...this.props} />
</div>
)
}
}
}
I am using this in this way, for example:
const TableHOC = withLoader(Table,true);
Now my table or any other component for example, will have its own well defined props interface. Everything is well typed.
However I am getting this issue
Argument of type '(props: ITableProps) => JSX.Element' is not assignable to parameter of type 'new () => Component<object, any, any>'.
Type '(props: ITableProps) => Element' provides no match for the signature 'new (): Component<object, any, any>'.ts(2345)
How can I solve this?
You'll want to use React.ComponentType
for the type instead:
const withLoader = <P extends object>(WrappedComponent: React.ComponentType<P>, loading: boolean) => {
// ...
}
Just a note though, if you're planning on toggling the value of loading
via a state change, you'll want to pass it as a prop instead. This is because every time you call withLoader
to get an enhanced component out, it is a new component, meaning that if you do it inside render
, React will always unmount and remount that rendered component. This also means that any state inside the enhanced component will be lost.
For example:
interface WithLoadingProps {
loading: boolean;
}
const withLoader = <P extends object>(
WrappedComponent: React.ComponentType<P>
) => {
return class WithLoading extends React.Component<P & WithLoadingProps, any> {
render() {
const { loading } = this.props;
return (
<div>
<div className={loading ? "loader" : ""}>
<WrappedComponent {...this.props} />
</div>
</div>
);
}
};
};
Sample use:
const MyComponent = ({ text }: { text: string }) => {
return <div>{text}</div>;
};
const MyLoadingComponent = withLoader(MyComponent);
class Foo extends React.Component<{}, { loading: boolean }> {
render() {
const { loading } = this.state;
return <MyLoadingComponent loading={loading} text="foo" />;
}
}
As a cherry on top, consider also adding the displayName
as instructed in React's documentation - this will enhance your debugging experience when working with React's devtools.
With:
Without:
interface WithLoadingProps {
loading: boolean;
}
const getDisplayName = <P extends object>(Component: React.ComponentType<P>) =>
Component.displayName || Component.name || "Component";
const withLoader = <P extends object>(WrappedComponent: React.ComponentType<P>) => {
class WithLoading extends React.Component<P & WithLoadingProps, any> {
static readonly displayName = `WithLoading(${getDisplayName(WrappedComponent)})`;
render() {
const { loading } = this.props;
return (
<div>
<div className={loading ? "loader" : ""}>
<WrappedComponent {...this.props} />
</div>
</div>
);
}
}
return WithLoading;
};