The following block of code was ripped out of a much larger app to illustrate what was breaking... If I ignore the error, the code itself executes just fine. Just the type hinting isn't liking it, and not sure why.
I do however believe it's something todo with that Omit
type.
I am presented with this error:
Argument of type '{ avatar: string; height: number; width: number; } & Pick<P & IInputProps, Exclude<keyof P, "firstName" | "lastName" | "avatar">>' is not assignable to parameter of type 'WrappedType<P>'.
Type '{ avatar: string; height: number; width: number; } & Pick<P & IInputProps, Exclude<keyof P, "firstName" | "lastName" | "avatar">>' is not assignable to type 'P'.
And the code is (or a gist):
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
interface FunctionComponent<P = {}> {
(props: P, context?: any): string;
}
// Our component props types
interface IInputProps {
firstName: string;
lastName: string;
avatar: string;
}
interface ISubProps {
width: number;
height: number;
}
// The helper type that takes an abstract prop type, adds the downstream ISubProps + a type that's based on our IInputProps
type WrappedType<P extends object = {}> = P &
ISubProps &
Omit<IInputProps, 'firstName' | 'lastName'>;
type SubComponent<P> = FunctionComponent<WrappedType<P>>;
type WrappedComponent<P> = FunctionComponent<P & IInputProps>;
function factory<P extends object = {}>(
Component: SubComponent<P>,
): WrappedComponent<P> {
// The props here are of type P & IInputProps
return ({ lastName, firstName, avatar, ...rest }) => {
const restString = Object.entries(rest)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
// -- THIS BIT DOESNT WORK
// Component's types are ISubProps + IInputProps (-firstName, -lastName) + P
const componentResponse = Component(
{
avatar,
height: 10,
width: 20,
...rest,
},
);
// -- TO HERE
return `FirstName: ${firstName}\nLastName: ${lastName}\n${restString}\n\n--BEGIN--\n${componentResponse}\n--END--`;
};
}
// Example impl
const test = factory<{ foo: string }>(props => {
return `hello: ${props.foo}, you have the avatar of ${
props.avatar
} with height ${props.height} and width ${props.width}`;
})({
firstName: 'firstName',
lastName: 'lastName',
avatar: 'avatar',
foo: 'foo',
});
console.log(test);
The problem is that Typescript is very limited in the math it can do on mapped and conditional types that contain unbound type parameters (such as P
in your case).
Although it might seem obvious to us, the compiler can't figure out that if you remove lastName, firstName, avatar
from P & { firstName: string; lastName: string; avatar: string; }
you get P
. As long as the parameter P
is in there the compiler will not try to resolve the type of rest
instead it will type rest as Pick<P & IInputProps, Exclude<keyof P, "lastName" | "firstName" | "avatar">>
There is no safe way to help the compiler along here, you will just have to use a type assertion to let the compiler know rest
will be P
const componentResponse = Component({
avatar,
height: 10,
width: 20,
...(rest as P),
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With