Search code examples
typescript

Why doesn't optional chaining placate TypeScript?


Let's say I have the following event handler (in React):

onClick={(e: MouseEvent<HTMLElement>) => {
     const link = e.target.href;

I understand why TypeScript complains about the above code: e.target could be an <a> tag (in which case it will have an href), or it could be some other element that doesn't have an href, and things could break if it doesn't.

But when I use the optional chaining operator, it shouldn't matter whether e.target has an href or not:

onClick={(e: MouseEvent<HTMLElement>) => {
     const link = e.target?.href;

And the same is true of this (more verbose) version:

onClick={(e: MouseEvent<HTMLElement>) => {
     if (!e.target.href) return;
     const link = e.target.href;

But no matter what, TypeScript gives me Property 'href' does not exist on type 'EventTarget'.ts(2339) error.

How can I express "I don't care whether e.target has an href or not" (without using @ts-ignore)?


Solution

  • Typescript complains because the HTMLElement interface doesn't have the href attribute. This is because this interface represents all the possible DOM elements, so it has their common properties.

    You should use HTMLAnchorElement instead.

    If you cant define e: MouseEvent<HTMLAnchorElement>, you can cast later in the function using const target = e.target as HTMLAnchorElement. Note that this approach is safe only if you are sure that e.target is an HTMLAnchorElement.

    If you are treating generic HTMLElements, you can use a type guard:

    const target = e.target
    if ("href" in target) {
      // here you can use `target.href`
    }
    

    Or, if you want a dedicated function

    const isAnchorElem = (target): target is HTMLAnchorElement => "href" in target
    const target = e.target
    if (isAnchorElem(target)) {
      // here `target` will be of type `HTMLAnchorElement`
    }