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
restwill be{b: number, c: number}because objecti1contains only three keys and one of them akaais exhausted. In the case ofrest2, TS can still infer type toObjas keys from interfaceI1are 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
restinside the function isPick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>.Excludeexcludes keysa,bandcfrom the generic typeT. Check Exclude here. Then,Pickcreates a new type fromI1 & Twith keys returned byExclude. SinceTcan 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 variablerestin the function remainsPick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>.Please note that type returned by
Pickis a subtype ofObjNow 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.createElementAnd calling it
The inferred type for
Pin the signature will beCin your code from the first argument i.e.componentbecause it has typeReact.ComponentType<C>. The second argument should be eitherundefinedornullorC(ignoringAttributesas of now). But the type ofcomponentPropsisPick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>, which is definitely assignable to{}but not toCbecause it is subtype of{}not ofC.Cis also a subtype of{}but the pick type andCmay 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
componentPropswill beCHope this answers your question and solves your problem.