import React, { Component, ComponentClass, ComponentType } from "react";
interface State {
hasError: boolean;
}
const withErrorBoundary = <T extends Record<string, never>>(
WrappedComponent: ComponentType<T>
): ComponentClass<T, State> =>
class ErrorBoundary extends Component<T, State> {
state: { hasError: boolean };
constructor(props: T) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
console.error(error);
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <p>Something went wrong</p>;
}
return <WrappedComponent {...this.props} />;
}
};
export default withErrorBoundary;
So I know what this class component does. What I'm trying to understand is <T extends Record<string, never>>(WrappedComponent: ComponentType<T>)
. I get it's a wrapper class but I can't wrap my head around it. Why is <T extends>
defined before the class component? Any resources that can solidify my understanding would be appricted.
CodePudding user response:
T
is a generic type parameter. Think of it like a variable for a type.
And it represents the props of the wrapped component so that the wrapper component can have the exact same props and pass them through.
So with this code:
const withErrorBoundary = <T extends Record<string, never>>(
WrappedComponent: ComponentType<T>
): ComponentClass<T, State> =>
class ErrorBoundary extends Component<T, State> {}
Let's go line by line.
const withErrorBoundary = <T extends Record<string, never>>(
This is a function named withErrorBoundary
which declares the generic parameter T
. T
is constrained by Record<string, never>
. This means that T
must be a subtype of the constraint. In other words, whatever T
is must be assignable to the constraint.
WrappedComponent: ComponentType<T>
ComponentType
is any react component, and it accepts a generic type for its' props. For example ComponentType<{ a: number }>
would be used like <MyComponent a={123} />
So this function accepts a single argument of type ComponentType<T>
. This is where T
infers its type. Whatever the type of the props of the component passed as WrappedComponent
is assigned to the type T
, because T
appears in the spot where ComponentType
expect the props type to be declared.
): ComponentClass<T, State> =>
The function returns a class based react component with props T
(the props that WrappedComponent
would accept) and a state type of State
(which is an interface you've declared elsewhere)
class ErrorBoundary extends Component<T, State> {}
This line creates a class component that extends a react component that has props of type T
and state of type State
. This matches the return value of the function, and will be returned by the function.
Lastly, this type:
T extends Record<string, never>
is a little strange. A Record
typed like that means an empty object (like {}
). So this function may only take components that have no props. Which doesn't make much sense:
const MyComp = () => <>test</>
const TestComp = withErrorBoundary(MyComp) // fine
const MyCompA = ({a}: { a: number }) => <>{a}</>
const TestCompA = withErrorBoundary(MyCompA) // type error
Instead, I'm guessing this type was intended:
T extends Record<string, unknown>
This means that T
is constrained by the type of an object with string
keys and unknown
typed values. This means the wrapped component can have any props.