Why does the following code not cause a compile-time error?
import * as React from 'react';
export class Test extends React.Component {
private _onReferenceUpdated = (ref: HTMLCanvasElement) => {
ref.width = 4; // This could throw, when ref is null
};
render(): JSX.Element {
return (
<canvas ref={this._onReferenceUpdated} />
);
}
}
The ref
attribute is inferred as
(JSX attribute) React.ClassAttributes<HTMLCanvasElement>.ref?: string | ((instance: HTMLCanvasElement | null) => void) | React.RefObject<HTMLCanvasElement> | null | undefined
which seems correct (the string
is a bit weird, but i guess that's just generally for attributes). How is (ref: HTMLCanvasElement) => void
assignable to (instance: HTMLCanvasElement | null) => void
?
Disclaimer: I only researched far enough to get an outline of the problem. If there is a proper explanation in a different question, or someone with a better understanding is willing to give one, feel free to add.
In Typescript 2.6, the strict function types setting was added, now checking function parameters contravariantly. Therefore,
const f = (p: HTMLCanvasElement) => void p;
const g: (p: HTMLCanvasElement | null) => void = f;
is an error with that setting enabled. However, sadly, due to what seems to be a design limitation, there is a problem with e.g. the ref
prop in TSX:
there is no way to tell TypeScript that the "ref" prop actually is contravariant
What follows is the bivarianceHack, or in other words, ref
behaves as-if strictFunctionChecks
was disabled. Then, HTMLCanvasElement
is a subtype of HTMLCanvasElement | null
, and accepted.
This is also visible, when following the type definition, which leads to:
type RefCallback<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"];
I find this quite sad (especially, that this hack isn't visible in the type annotation, at least not in vscode), and any proper solutions, or updates on the topic, are welcome. At the very least, i am considering an ESLint typescript rule, that just forces the parameter of any function passed to ref
to be nullable, in a hardcoded way.