The compiler find the definition of HTMLElement
, but the definition from react/global.d.ts
is taken instead of typescript/lib/lib.dom.d.ts
.
Here's the compiler error:
Property 'value' does not exist on type 'EventTarget'. TS2339
Here are the differences:
interface Element { }
interface HTMLElement extends Element { }
interface HTMLElement extends Element, GlobalEventHandlers, DocumentAndElementEventHandlers, ElementContentEditable, HTMLOrSVGElement, ElementCSSInlineStyle {
accessKey: string;
readonly accessKeyLabel: string;
autocapitalize: string;
dir: string;
draggable: boolean;
hidden: boolean;
innerText: string;
lang: string;
readonly offsetHeight: number;
readonly offsetLeft: number;
readonly offsetParent: Element | null;
readonly offsetTop: number;
readonly offsetWidth: number;
spellcheck: boolean;
title: string;
translate: boolean;
click(): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
So my question is: how to make the compiler choose typescript/lib/lib.dom.d.ts
before the one from React? Any help would be greatly appreciated!
Edit: here's the tsconfig.json
, correctly referencing the TS lib:
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"incremental": true, // Enable incremental compilation by reading/writing information from prior compilations to a file on disk
"sourceMap": true, // Generate corrresponding .map file
"declaration": true, // Generate corresponding .d.ts file
"noUnusedLocals": true, // Report errors on unused locals
"noUnusedParameters": true, // Report errors on unused parameters
"jsx": "react"
},
"include": [
"src"
]
}
react/global.d.ts
exists as a placeholder for the sole purpose of allowing the compilation of React without depending on Typescript's DOM libary.
If this was not done, various parts of React would fail to compile unless --lib DOM
(or the equivalent in tsconfig.json
) is specified because of missing interface declarations.
The real DOM interfaces are declared in the DOM libary included in Typescript (i.e. typescript/lib/lib.dom.d.ts
).
Both of them can coexist because Typescript merges interface
s with equal names as long as they don't contain conflicting declarations.
However, none of that is the cause of this problem.
If we take a look at the declaration of React.FormEvent
and its sibling React.ChangeEvent
for comparison, we'll notice that target
is not specified in the former. If we dig deeper into the inherited interfaces, we'll eventually find that target
is of type EventTarget
.
interface FormEvent<T = Element> extends SyntheticEvent<T> {
}
interface ChangeEvent<T = Element> extends SyntheticEvent<T> {
// Note that target is also a T (e.g. HTMLInputElement) here
target: EventTarget & T;
}
// ...
interface SyntheticEvent<T = Element, E = Event>
extends BaseSyntheticEvent<E, EventTarget & T, EventTarget> {}
// ...
interface BaseSyntheticEvent<E = object, C = any, T = any> {
// ...
target: T; // T is EventTarget at this point
// ...
}
A React.FormEvent
doesn't have any particular element type as target
, only a generic EventTarget
.
This particular type of event can be captured at some parent element (e.g. a <form>
) after propagating up from child elements (e.g. an <input>
).
At the point of capture, the type of the originating element (target
) is unknown during compilation and can thus not be declared exactly.
If the event is captured directly on a particular element of type T
(e.g. an <input>
or HTMLInputElement
), it is actually of type React.ChangeEvent<T>
. For example:
<input ... onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
// It is known that target is an <input> element
console.log(e.target.value);
}} />
If you want to capture this event at a parent element, you will have to determine the correct type of target
manually (based on your own knowledge beyond what the compiler can infer) and cast it appropriately:
<form ... onChange={(e: React.FormEvent<HTMLFormElement>) => {
// I swear that the target is really an <input> element
const target = e.target as HTMLInputElement;
console.log(target.value);
}}>
<input ... />
</form>
update*
e.target
is an HTMLElement, however it is NOT guaranteed to have the value
property.
Try to cast event.target to the HTML element to ensure it is HTMLInputElement
(<HTMLInputElement>e.target).value
ref: https://angular.io/guide/user-input#!#type-the--*event*
old answer*
Try to use a type-assertion: current as HTMLElement
I can see the element
is HTMLElement, however, you should check current
.
I cannot see the initialization of current
well because it is hidden by error message window.
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