Here we need to understand some utility types and how the destructuring happens in TS.
type Obj = {
[key: string]: any
}
interface I1 {
a: number
b: number
c: number
}
const i1: I1 = {
a: 1,
b: 1,
c: 1,
}
let {a, ...rest} = i1
interface Obj {
[key: string]: any
}
const i2: Obj & I1 = {
a: 1,
b: 1,
c: 1,
d: 1,
e: 1,
}
let {a: a1, b, c, ...rest2} = i2
function func<T extends Obj>(param: I1 & T) {
const {a, b, c, ...rest} = param
}
In the above code, the inferred type for rest will be {b: number, c: number} because object i1 contains only three keys and one of them aka a is exhausted. In the case of rest2, TS can still infer type to Obj as keys from interface I1 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 is Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>. Exclude excludes keys a, b and c from the generic type T. Check Exclude here. Then, Pick creates a new type from I1 & T with keys returned by Exclude. Since T 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 to Obj. That's why the type variable rest in the function remains Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>.
Please note that type returned by Pick is a subtype of Obj
Now coming to the question, the same situation happens with componentProps. The type inferred will be Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>. TS will not be able to narrow it down. Looking at the signature of React.createElement
function createElement<P extends {}>(
type: ComponentType<P> | string,
props?: Attributes & P | null,
...children: ReactNode[]): ReactElement<P>
And calling it
React.createElement(component, componentProps)
The inferred type for P in the signature will be C in your code from the first argument i.e. component because it has type React.ComponentType<C>. The second argument should be either undefined or null or C (ignoring Attributes as of now). But the type of componentProps is Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>, which is definitely assignable to {} but not to C because it is subtype of {} not of C. C is also a subtype of {} but the pick type and C 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 error
'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 '{}'.
As 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
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 as unknown as C);
// ------------------------------------------------^^^^^^^^
};
This is definitely a correct type assertion because we know that type of componentProps will be C
Hope this answers your question and solves your problem.