I have the following code:
type XType<T> = {
x: T;
};
function extractTypes<Props, T>(Component: React.FC<Props & XType<T>>) {
return (props: Props, value: T) => {
Component({ ...props, x: value });
};
}
const CompA: React.FC<{ a: string } & XType<string>> = () => <div>HELLO</div>;
const CompB: React.FC<{ b: string } & XType<number>> = () => <div>WORLD</div>;
extractTypes<{ a: string }, string>(CompA)({ a: 'A' }, 'X'); // OK
extractTypes<{ b: string }, number>(CompB)({ b: 'B' }, 1); // OK
extractTypes(CompA)({ a: 'A' }, 'X'); // Compile error
extractTypes(CompB)({ b: 'B' }, 1); // Compile error
CompA
and CompB
expect a
and b
properties along with all properties in XType
.
The 3rd-last and 4th-last compile correctly, however the Typescript compiler complains on the last two lines as follows:
Argument of type '{ a: string; }' is not assignable to parameter of type 'PropsA & XType<string>'.
Property 'x' is missing in type '{ a: string; }' but required in type 'XType<string>'.ts(2345)
Argument of type '{ b: string; }' is not assignable to parameter of type '{ b: string; } & XType<number>'.
Property 'x' is missing in type '{ b: string; }' but required in type 'XType<number>'.ts(2345)
I've tried many approaches to ensure that I can call extractTypes
without specifying the types explicitly and still ensure that the method returned by this function accepts only { a: string }
or { b: string }
instead of { a: string; x: string }
or { b: string; x: number }
, but to no avail.
Is there any way I can accomplish this without requiring explicit specifying generic types for extractTypes
?
I think I need to find a way to create a type by subtracting one type (XType
) from another (Props
). I've tried using Omit<Props, keyof XType<T>>
but it doesn't work well here.
Thanks!
Asim
I would simply extend XType
. This will force you to assert a type internally though.
// adding a default for convenience
type XType<T = unknown> = {
x: T;
};
function extractTypes<Props extends XType>(Component: React.FC<Props>) {
// simply index and omit keys in `Props`
return (props: Omit<Props, keyof XType>, value: Props['x']) => {
Component({ ...(props as Props), x: value });
// assertion --------------
};
}
If you want excess property checking to also work when you pass a reference as props, you can use Omit<Props, keyof XType> & {[k in keyof XType]?: never}
but this may be overkill given the way people use React.
As a side-note, merging the 2 generics was not strictly necessary and your original design is safer, because removing x: value
will yield an error
function extractTypes<Props, T>(Component: React.FC<Props & XType<T>>) {
return (props: Omit<Props, keyof XType>, value: T) => {
Component({ ...(props as Props), x: value });
// assertion ---------------
};
}
I don't know why though because Props
is inferred as {a: string} & XType<string