I'd like to have a variable (actually function argument, but I simplified the problem) that can hold a React component that MUST have e.g. aNumber: number among it's properties.
My unsuccesfull try was:
function MyComp(props: { aNumber: number, aString: string }) {
return <>{props.aNumber} {props.aString}</>;
}
let f2: FunctionComponent<{ aNumber: number }> = MyComp;
giving me the error:
Type '(props: { aNumber: number; aString: string; }) => Element' is not assignable to type 'FunctionComponent<{ aNumber: number; }>'.
Types of parameters 'props' and 'props' are incompatible.
Property 'aString' is missing in type 'PropsWithChildren<{ aNumber: number; }>' but required in type '{ aNumber: number; aString: string; }'.ts(2322)
FunctionComponent is defined in react as:
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
I don't understand the error. If I defined a var w/ a constraint to have aNumber, and I assigned to it a richer element => why isn't it happy? And why it complains about those "richer" attributes?
Of course, the following works:
let f1: FunctionComponent<any> = MyComp;
Usually when I try advanced typing constructs in TS, I finally get it working, after a lot of head aches. This time it doesn't seem advanced, but I didn't manage to figure it out.
Many thanks in advance.
The problem is that React components are functions and functions are contravariant, meaning that if a function with parameters of type T is required then it can be satisfied with a function of a wider type than T but not a narrower type. That is as opposed to, say, plain objects, which are covariant, i.e., that will accept a narrower type, but not a wider one.
You can see the same thing without any React code
function MyFunc(props:{a: number, b: string}){return}
let func2: (props:{a:number})=>void = MyFunc
// ^-- error because MyFunc is narrower than typeof func2
An intuitive way to think of this is from the perspective of the consumer calling your function/React component. In the case where they are expecting to call a function of typeof f2, they are expecting to only need to pass it props of aNumber and yet by assigning a narrower function to f2, they would now need to also pass aString, which they were not expecting.
This issue comes up a lot in React for an understandable reason. Imagine you have a <Button> component, that takes one prop, and a <FancyButton> component that takes multiple props. It's tempting to think, well, if I need any old <Button> than a <FancyButton> will do just as well. That sort of sounds like simple inheritance. But if you think about it a little more, there's an error. If I'm a component expecting to need to insert a <Button> as one of my children, that means I'm only expecting to need to pass it one prop. If I now swap in a <FancyButton> a bunch of props are now expected by that component that I wasn't expecting to provide.
The intuitive point is that you aren't just displaying the <Button> component--in which case you could just not care about the fact that the <FancyButton> has more "fanciness". It's that you actually need to provide inputs--props--to the component, and so a narrower one will require you (the parent component) to provide more.
The same logic is of course generally true for all functions, but I guess it tends to come up more often in React because we tend to think of components as "objects to display".
Here is a more expansive SO answer on this issue generally (which is tricky at first!): Difference between covariant and contravariant positions in Typescript
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