I get this error from TS and it doesn’t make any sense,
because my T generic is fully compatible with types A | B, because it extends from it! It’s is not true what the error says that you can instantiate it with a incompatible type.
type MyProps<T extends A | B> = {
initial: T;
onChange: (value: T) => void;
}
function MyComponent<T extends A | B>(props: MyProps<T>){
const changeHandler = (value: T) => {
...
props.onChange(value);
};
return <SomeOtherComponent onChange={changeHandler} ... />
}
The error is thrown forthe onChange
property of SomeOtherComponent, but the type of onChange in that component is (value: A | B) => void
, so it’s fully compatible with T.
What am I missing here?
3
Answers
In this case
T extends A | B
, your change handler is accepting the value of typeT
, while inSomeOtherComponent
‘sonChange
method you have a function that accepts the value of typeA | B
Here we are narrowing down the scope
T extends A | B
butT
can not be assigned to theA | B
type. If you change yourSomeOtherComponent
like below it will workwhen your function definition is like this
what you are doing is passing this function definition to your
SomeOtherComponent
‘sA | B
type which is like passingT
->A | B
but there could be reverse way aroundA | B
->T
.SomeOtherComponent
expects anonChange
property of type(value: A | B) => void
; that is, it expects anonChange
property that will accept any value of either typeA
or typeB
.You’re trying to give it an
onChange
property of type(value: T) => void
, whereT
could be (for example) justA
or justB
; that is, you’re trying to give it anonChange
property that may not accept certain values that it needsonChange
to accept.The technical term that you can search for is contravariance; you can use a function with more-permissive parameter-types than is expected, but not one with more-restrictive parameter-types, because then you’re imposing requirements on the caller that it may not satisfy.
As other answers have pointed out it’s wise to ensure you’re not narrowing the type, by keeping the
T extends A | B
all the way down.In instances like this (where I am deliberately creating a function to be used for the value of a known prop); I find that typing
changeHandler
upfront can be useful as you can expect thatchangeHandler
will be of the correct type.If the prop is just being passed through and called, maybe I wouldn’t bother with redefining it, but I assume this has just been done for the purpose of the question.