Imagine a flexible component that takes a React.ComponentType
and its props
and renders it:
type Props<C> = {
component: React.ComponentType<C>;
componentProps: C;
otherProp: string;
};
const MyComponent = <C extends {}>(props: Props<C>) => {
return React.createElement(props.component, props.componentProps);
};
Can I somehow let MyComponent
receive the dynamic props
directly, e.g. like that (not working):
type Props<C> = {
component: React.ComponentType<C>;
otherProp: string;
};
const MyComponent = <C extends {}>(props: Props<C> & C) => {
const { otherProp, component, ...componentProps } = props;
return React.createElement(component, componentProps);
};
Error:
Error:(11, 41) TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is not assignable to parameter of type 'Attributes & C'.
Type 'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is not assignable to type 'C'.
'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is assignable to the constraint of type 'C', but 'C' could be instantiated with a different subtype of constraint '{}'.
Here we need to understand some utility types and how the destructuring happens in TS.
In the above code, the inferred type for
rest
will be{b: number, c: number}
because objecti1
contains only three keys and one of them akaa
is exhausted. In the case ofrest2
, TS can still infer type toObj
as keys from interfaceI1
are exhausted. By exhausted I mean they are not captured using rest operator.But in case of function, TS is not able to do this type of inference. I don't know the reason why TS is not able to do. That may be due to limitation generics.
What happens in case of a function is that the type for
rest
inside the function isPick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>
.Exclude
excludes keysa
,b
andc
from the generic typeT
. Check Exclude here. Then,Pick
creates a new type fromI1 & T
with keys returned byExclude
. SinceT
can be any type, TS is not able to determine the keys after exclusion and hence the picked keys and hence the newly created type even though T is constrained toObj
. That's why the type variablerest
in the function remainsPick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>
.Please note that type returned by
Pick
is a subtype ofObj
Now coming to the question, the same situation happens with
componentProps
. The type inferred will bePick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>
. TS will not be able to narrow it down. Looking at the signature ofReact.createElement
And calling it
The inferred type for
P
in the signature will beC
in your code from the first argument i.e.component
because it has typeReact.ComponentType<C>
. The second argument should be eitherundefined
ornull
orC
(ignoringAttributes
as of now). But the type ofcomponentProps
isPick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>
, which is definitely assignable to{}
but not toC
because it is subtype of{}
not ofC
.C
is also a subtype of{}
but the pick type andC
may or may not be compatible (this is same as - there is a class A; B and C derives A, objects of B and C are assignable to A, but object of B is not ascribable to C). That's why the errorAs we are more intelligent than TS compiler, we know that they are compatible but TS does not. So make TS believe that we are doing correct, we can do a type assertion like this
This is definitely a correct type assertion because we know that type of
componentProps
will beC
Hope this answers your question and solves your problem.