Reputation: 30176
Typescript allows Covariance. The following code is valid, even though createElement('p')
is strongly typed and returns an HTMLParagraphElement
:
let p: HTMLElement = document.createElement('p');
At the same time, the following tsx code is not valid:
export default function RefProblem() {
const myRef = useRef<HTMLElement>(null);
return <p ref={myRef}>text</p>;
}
It produces an error message:
Type 'RefObject<HTMLElement>' is not assignable to type 'RefObject<HTMLParagraphElement>'.
This is a surprise to me, I always thought that the typechecks in Typescript would allow covariance in generic classes. So I went to the typescript playground and created this example. Everything behaves just as expected:
class A { aval: number = 0; };
class B extends A { bval: number = 0; };
let a: A = new A();
let b: B = new B();
a = b; // ok
b = a; // error
class G<T>{ gval?: T };
let ga: G<A> = new G();
let gb: G<B> = new G();
ga = gb; //ok
gb = ga; //error
Why does covariance not work for generic react refs?
Upvotes: 4
Views: 293
Reputation: 18556
This is probably due to the fact that React’s typings predate TypeScript’s variance annotations.
useRef
is typed to return RefObject<T>
. If RefObject<T>
were declared as RefObject<in T>
, the check would succeed, whereas the inverse (assigning a RefObject<HTMLParagraphElement>
to a ref
on a <span>
) wouldn’t.
Upvotes: 2
Reputation: 30176
While testing the code I realized my mistake. Maybe this will be useful to someone else, so I'll still post the question.
I thought that the reference will wrap a RefObject<HTMLParagraphElement>
in the supertype RefObject<HTMLElement>
. This should be fine.
But before this can happen, the react ref that has been created needs to be assigned to the JSX attribute. And here it's the inverse. I'm trying to assign a RefObject<HTMLElement>
to an attribute of type RefObject<HTMLParagraphElement>
.
So the typecheck makes a completely valid complaint. This should fail.
Upvotes: 0