Definitelytyped: Make React.SyntheticEvent.target generic over T, not React.SyntheticEvent.currentTarget

Created on 26 Sep 2016  ·  7Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

Currently, the Typescript 2.0 signature for React's SyntheticEvent looks like:

interface SyntheticEvent<T> {
    currentTarget: EventTarget & T;
    target: EventTarget;
}

According to commit a13fa7abf55daedf25b31258b908548f55962c7a, target was originally EventTarget & T, but this change was (apparently) accidentally reverted during a merge in commit 5607f54defce88bc52a0440288f434cafffdb5ce.

The current signature breaks a lot of pre-2.0 code (as you can imagine, React code is replete with event handlers) for little to no benefit (since most code relies on event.target, so this means still relying on pre-2.0 code like (event.target as any).value, since event.target is not generic.

I'll be happy to provide a patch, but I'd like to be sure SyntheticEvent.target was not de-generified on purpose.

Most helpful comment

You cannot always tell target's type at compile time. Making it generic is of little value.
target is the origin of the event (which no one really cares about, it might be a span inside a link, for example)
currentTarget is the element that has the event handler attached to, which you should very much care about and type accordingly if you attached a dataset or other attributes to it, and intend to access at runtime.

Relying on target instead of currentTarget is a beginner's mistake that will bite them sooner than latter.

For example:

  handleTabClick = (e: React.SyntheticEvent<HTMLLinkElement>) => {
    e.preventDefault()
    // !!! e.target.dataset is empty if user clicked the <span>, not the <a> !!!
    // !!! how can you type e.target? User might also have clicked the <a> if it has padding !!!
    // you have to use currentTarget, and it is the element that you know the type for sure
    if (e.currentTarget.dataset['closable'] === 'true') {
      this.close()
    }
  }

render() {
  return <a onClick={this.handleTabClick} data-closable="true">
      <span className="fatty">Click me!</span>
   </a>
}

This code used to compile, as it should.

With the 'fix' merged, e.currentTarget is not typable (I can't access its dataset without a type error) and e.target is now typed as an HTMLLinkElement, when in fact there is no way we can be sure about it. (I may have clicked the span inside.

Theoretically we could add a second generic to SyntheticEvent, to type target, when user is certain about it (no children to the event's handler, or they all have the same type). But it is of little value and really misleading (because it only applies to particular cases where using casting would make a lot more sense).
In particular, if you have no children, you can use currentTarget.

All 7 comments

This is probably an mistake made during merging. Please make a PR.

see #11041, #10784 and many more. Is a mystery why it is not fixed even after merge.

You cannot always tell target's type at compile time. Making it generic is of little value.
target is the origin of the event (which no one really cares about, it might be a span inside a link, for example)
currentTarget is the element that has the event handler attached to, which you should very much care about and type accordingly if you attached a dataset or other attributes to it, and intend to access at runtime.

Relying on target instead of currentTarget is a beginner's mistake that will bite them sooner than latter.

For example:

  handleTabClick = (e: React.SyntheticEvent<HTMLLinkElement>) => {
    e.preventDefault()
    // !!! e.target.dataset is empty if user clicked the <span>, not the <a> !!!
    // !!! how can you type e.target? User might also have clicked the <a> if it has padding !!!
    // you have to use currentTarget, and it is the element that you know the type for sure
    if (e.currentTarget.dataset['closable'] === 'true') {
      this.close()
    }
  }

render() {
  return <a onClick={this.handleTabClick} data-closable="true">
      <span className="fatty">Click me!</span>
   </a>
}

This code used to compile, as it should.

With the 'fix' merged, e.currentTarget is not typable (I can't access its dataset without a type error) and e.target is now typed as an HTMLLinkElement, when in fact there is no way we can be sure about it. (I may have clicked the span inside.

Theoretically we could add a second generic to SyntheticEvent, to type target, when user is certain about it (no children to the event's handler, or they all have the same type). But it is of little value and really misleading (because it only applies to particular cases where using casting would make a lot more sense).
In particular, if you have no children, you can use currentTarget.

Plus it is completely wrong, on a DOMAttributes<SomeType>, you get a onClick: MouseEventHandler<SomeType>, then get EventHandler<MouseEvent<SomeType>> with

    interface EventHandler<E extends SyntheticEvent<any>> {
        (event: E): void;
    }

You absolutely cannot type target differently from currentTarget. onClick expects an event handler that has handling event (aka currentTarget) as the generic. It cannot be something else, it won't compile.

@bbenezech

handleTabClick = (e: React.SyntheticEvent<HTMLLinkElement>) => { ... }

I suggest instead:

handleTabClick = (e: React.FormEvent<HTMLLinkElement>) => { ... }

You also have ClipboardEvent<T>, CompositionEvent<T>, DragEvent<T>, FocusEvent<T>, KeyboardEvent<T>...
SyntheticEvent is the low level thing.

This is still like this on the @types NPM repository. Any update on when this will be published

This was changed back. See #12239.

Was this page helpful?
0 / 5 - 0 ratings