i'm trying to partition a custom type back into it's individual elements:
type CustomType<T extends React.ElementType> = React.ComponentPropsWithoutRef<T> & { aBunchOfProps: string; }
code looks like following:
const partitionProps = <T extends React.ElementType>(
props: CustomType<T>
): {
customProps: { aBunchOfProps: string }, // named type
componentProps: ComponentPropsWithoutRef<T>
} => {
const {
aBunchOfProps,
...componentProps
} = props;
const customProps = { aBunchOfProps };
return { customProps, componentProps };
} // Error! componentProps: Omit<CustomType<T>, { aBunchOfProps }> is
// not assignable to type ComponentPropsWithoutRef<T>
which is weird because I'm able to assert type equality
type Equals<T, U> = T extends U ? (U extends T : true : false) : false;
type AreTheyEqual<T extends React.ElementType> = Equals<
Omit<CustomType<T>, { aBunchOfProps: string }>,
React.ComponentPropsWithoutRef<T>
>;
type UsingDiv = AreTheyEqual<'div'>; // true
type UsingA = AreTheyEqual<'a'>; // true
type UsingIFrame = AreTheyEqual<'iframe'>; //true
there should be maybe some conditional type to assert type equality in the partition function, but I can't quite figure it out
type AssertEquality<T extends React.ElementType> = Equals<T1, T2> extends true ? React.ComponentPropsWithoutRef<T> : never;
but that doesn't quite work either.
Any ideas?
Edit 11/1/21: See this typescript playground for a reproduction. We're able to force cast the return type as React.ComponentPropsWithoutRef<T>
but it still leaves the function and exists inside of calling components as any
.
The problem here is that you are using generic inside the function
const partitionProps = <T extends React.ElementType>(
props: CustomType<T>
): { /** ....some code */ }
T
is like black box, it will be known only during the call of a function, whereas comparison of :
type UsingDiv = AreTheyEqual<'div'>; // true
type UsingA = AreTheyEqual<'a'>; // true
type UsingIFrame = AreTheyEqual<'iframe'>; //true
is much easier to to because generic parameter of AreTheyEqual
is known at compile time : div
, a
, iframe
.
Imagine, we don't have a generic type in our function:
const partitionProps = (
props: SomeHelpers & ComponentPropsWithoutRef<'a'>
): {
customProps: { aBunchOfProps: string },
componentProps: ComponentPropsWithoutRef<'a'>
} => {
const {
aBunchOfProps,
...componentProps
} = props;
const customProps = { aBunchOfProps };
return { customProps, componentProps };
}
There are no errors, because TS is able to infer exact type of componentProps
.
Once you have provided T
, TS is no more sure about type equality.
Since, it is unsafe to return
{
customProps: { aBunchOfProps: string },
componentProps: ComponentPropsWithoutRef<T>
}
However, you can loose the strict behavior. You can overload your function. Overloadings are bivariant.
type SomeHelpers = { aBunchOfProps: string };
type CustomType<T extends React.ElementType> = SomeHelpers &
React.ComponentPropsWithoutRef<T>;
function partitionProps<T extends React.ElementType<{ tag: number }>>(
props: CustomType<T>
): {
customProps: { aBunchOfProps: string }; // named type
componentProps: ComponentPropsWithoutRef<T>;
};
function partitionProps<T extends React.ElementType>(props: CustomType<T>) {
const { aBunchOfProps, ...componentProps } = props;
const customProps = { aBunchOfProps };
return { customProps, componentProps };
}
const result = partitionProps({
aBunchOfProps: 'sdf',
tag: 42
});
Please keep in mind that you also should provide a generic argument to React.ElementType
because if you don't generic parameter will be any
.
See ElementType
signature:
type ElementType<P = any> =
{
[K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
}[keyof JSX.IntrinsicElements] |
ComponentType<P>;