Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTMLElement definition in typescript.lib/lib.dom.d.ts was overridden by @types/react/global.d.ts

enter image description here

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:

In react/global.d.ts

interface Element { }

interface HTMLElement extends Element { }

In typescript/lib/lib.dom.d.ts

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"
  ]
}
like image 209
foolyoghurt Avatar asked Jan 13 '19 09:01

foolyoghurt


2 Answers

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 interfaces 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>
like image 103
Lukas Avatar answered Oct 12 '22 08:10

Lukas


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.

like image 37
Chanran Kim Avatar answered Oct 12 '22 10:10

Chanran Kim