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.
