I have a TypeScript function with the signature:
function connectRRC<C extends ComponentType<any>>(component: C)
which currently accepts any component.
I'd like to accept ONLY components that have in "props" a given property. E.g. "id: string".
Doing:
function connectRRC<C extends ComponentType<{ id: string }>>(component: C)
doesn't work, cf. this explanation.
ComponentType
is defined in React like:
type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;
interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
new (props: P, context?: any): Component<P, S>;
propTypes?: WeakValidationMap<P>;
contextType?: Context<any>;
contextTypes?: ValidationMap<any>;
childContextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
Thanks in advance for any hints!
@sam256 provided a partial working answer, which I found acceptable. However, I found additional use cases that don't work, w/o finding a reasonable explanation. This TS playground link shows them.
This works, as expected:
class GoodComponent2 extends React.Component<{id:string, otherProp:number} & { somethingElse: boolean }> {}
connectRRC(GoodComponent2)
But the following don't work:
class BadComponent3<P = { somethingElse: boolean }> extends React.Component<{id:string, otherProp:number} & P> {}
connectRRC(BadComponent3)
class BadComponent4<P = {}> extends React.Component<{id:string, otherProp:number} & P> {}
connectRRC(BadComponent4)
I have a partial answer.
type RequiredProps = {id:string}
function connectRRC<T extends RequiredProps>(component: React.FC<T>){}
const goodComponent = (props:{id:string, otherProp:number}) => <div />
const badComponent = (props:{otherProp:number}) => <div />
connectRRC(goodComponent)
connectRRC(badComponent) //typescript error
This works pretty much as you want because Typescript is now inferring T
and making sure it extends RequiredProps
.
The only problem is this won't generate an error for a component passed with no props at all. This doesn't generate an error:
const noPropsComponent = (props:{})=><div />
connectRRC(noPropsComponent)
The reason is contravariance again, at least in part, though I don't completely understand what the compiler is doing here.
What appears to be happening is TS does not infer T
as {}
and therefore failing to satisfy the constraint (can anyone explain why not?)
Instead, it looks like it assumes T
is at least RequiredProps
(the constraint) and then just checks if component
satisfies React.FC<RequiredProps>
, which it does because RequiredProps
is in the contravariant position.
I'm not sure of a good way to get past this, in part because I'm not sure I totally get why TS is behaving this way. Specifically, I don't understand why it doesn't infer T
as {}
and fail it for not satisfying the RequiredProps
constraint.
Hopefully someone else on the forum can help...
Playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With