Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make React.SyntheticEvent.target generic over T, not React.SyntheticEvent.currentTarget #11508

Closed
Emm opened this issue Sep 26, 2016 · 9 comments

Comments

@Emm
Copy link
Contributor

Emm commented Sep 26, 2016

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

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

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

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.

@ghost
Copy link

ghost commented Sep 26, 2016

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

@bogusfocused
Copy link

bogusfocused commented Sep 26, 2016

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

@bbenezech
Copy link
Contributor

bbenezech commented Oct 25, 2016

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.

@bbenezech
Copy link
Contributor

bbenezech commented Oct 25, 2016

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.

@tkrotoff
Copy link
Contributor

@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.

@ghost
Copy link

ghost commented Dec 16, 2016

This was changed back. See #12239.

DragorWW added a commit to DragorWW/react-select that referenced this issue Jan 24, 2019
DragorWW added a commit to DragorWW/react-select that referenced this issue Jan 24, 2019
marianacapelo pushed a commit to OutSystems/react-select that referenced this issue Aug 11, 2021
sh0ji added a commit to wwnorton/design-system that referenced this issue Sep 15, 2021
* feat(popper): add show delay

addresses [NDS-7]

* chore: allow externalTo to be null

* refactor(popper): add new Popper implementation

this uses react-popper as our base implementation
and adds transitions via react-transition-group

* refactor(popover): use new Popper component

* refactor(tooltip): use new Popper component

* refactor(popper): promote popper sass to real component

* refactor(tooltip): use updated popper tokens

* refactor(popover): use updated popper tokens

* chore: restructure Popover and Tooltip files

index.ts exports public API

* chore: improve basic dropdown story

* feat(motion): add fade transition

* refactor(button): destructure props in one place

* feat(button): set a default tooltip show delay when iconOnly

icon-only buttons should delay their tooltip
so that hovering a row of buttons doesn't
cause all tooltips to show at the same time.

* chore: update lockfile w/react-popper & react-transition-group

* docs(tooltip): add showDelay knob to applicable stories

* refactor(popover): use "distance" for offset token

* refactor(tooltip): use "distance" for offset token

* refactor: import TooltipCoreProps from the new types file

* docs(popover): add distance knob to minimal story

* refactor: remove Element check

* Create table.mdx

Added usage documentation for the table component.

* refactor(text-field): update email regex

addresses NDS-18, #152

* feat: initial badge implementation

* feat(tag): initial tag implementation

* feat(badge): initial badge implementation

* refactor(badge): changed color token for pill and dot variant

* refactor(badge): added props and removed icon for dot variant

* fix(badge): update as per code reviewed suggestions

* fix(badge): update as per code reviewed suggestions

* fix(badge): removed unwated css span

* refactor(badge): removed icon prop and done code clean up after remove icon

* fix(badge): update as per code reviewed suggestions

* chore: add default flag for tokens

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>

* refactor: use type checking instead of assertion

* Revert "feat(button): set a default tooltip show delay when iconOnly"

This reverts commit 877f07d.

* test(tooltip): update hover/unhover tests

* chore: add unhover to dictionary

* refactor: reference can be an SVGElement

* chore(icon): use userEvent instead of fireEvent

* test(button): use userEvent insead of fireEvent where possible

* test(button): fix live region test

* chore(deps): bump codecov/codecov-action from 1 to 2.0.2

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 2.0.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](codecov/codecov-action@v1...v2.0.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* docs(website): initial content guidelines

* Create index.mdx

I'll need Wilmer/Anand's help with setting up the index when we have more content. Just adding this as a placeholder so I can add the content guidelines that we have so far.

* Create usable-writing-guidelines.mdx

* Update usable-writing-guidelines.mdx

Added the remaining sections of the Usable Writing Guidelines.

* Update usable-writing-guidelines.mdx

Trying to fix the links.

* chore: format with prettier

* chore: use apostrophes

* chore: add spelling ignored words

* chore: reformat principles

* chore: fix resources links; make lists

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>

* fix(disclosure): properly render flex summary on Safari [NDS-93] (#187)

* fix: wrap disclosure's summary in div

* fix: Revert {...attributes} back to summary + temp fix comment

* fix: typo

* feat(tooltip): set a default show delay of 400ms

* fix(button): pass ARIA labelling attributes to button

* test(switch): update tooltip test

* chore: import from the right location

* chore: require node 14 for development

use of the nullish coalescing operator makes this required

* chore: remove unused token utility

this wasn't adding much, so components that need to retrieve a CSS
variable should do it themselves.

* test: fix live region test

* test(disclosure): fix failing tests

* fix(select-hook): use the correct form target

DefinitelyTyped/DefinitelyTyped#11508 (comment)

* chore(deps): yarn upgrade

* chore(deps): update storybook to 6.3.7

* chore(deps): upgrade commitlint

* chore(deps): upgrade @Material

* chore(deps): upgrade @testing-library

* chore(deps): gulp-sass upgrade

* chore(deps): upgrade husky major v7

* chore(deps): upgrade jsdom v17

* chore(deps): upgrade ts-node v10

* chore: offset tuple is optional

* refactor: error if onChange triggers without a checked attribute

* refactor(website): re-swizzle NavBar

* refactor(website): use nds color scheme switch in navbar

* refactor(core): use new sass div syntax

command: npx sass-migrator division **/*.scss
reference: https://sass-lang.com/d/slash-div

* refactor(website): remove unused var

* fix(core): remove default focus z-index

closes NDS-21

* chore(deps-dev): bump docusaurus-plugin-sass to v0.2.1

command used: yarn upgrade-interactive --latest
reference: https://github.com/rlamana/docusaurus-plugin-sass

* Revert "chore(deps-dev): bump docusaurus-plugin-sass to v0.2.1"

This reverts commit 2131bc5.

* chore: allow node v12 for development

tests won't run with node <14 because of how we're compiling tests

* refactor(core): undeclared properties cannot be null

we should consider whether these should be 'unset'

* chore(deps-dev): bump docusaurus-plugin-sass to v0.2.1

command used: yarn upgrade-interactive --latest
reference: https://github.com/rlamana/docusaurus-plugin-sass

* feat(tag): initial tag implementation

* feat(tag): initial tag implementation

* refactor(tag):  changed htmlattributes htmlelement to htmlspanelement

* refactor(badge): as per the design team suggestions

* fix(tag): fixed tag dismissable icon

* refactor(tag): fixed default as a button and for link is span

* fix(tag): as per the code review suggestions made the changes

* refactor(tag): updated the code for identifying child node is link in utility

* refactor(tag): made changes as per the code reviews

* refactor(react): export link hook and functions

* refactor: remove unused prop

* refactor: render conditionally based on AC

- when dismissible, only the dismiss button should be clickable
- when not dismissible & contains a link, the link should be clickable
- when not dismissible and not a link, the tag should be clickable

* refactor: update styling for new rendering

this simplifies the anatomy & tokens, and more closely matches the designs

* refactor: use type predicate; simplify description

* refactor: tags can't be both dismissible and a link

* chore: use knobs for link story rendering

* test(tag): add tests

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>

* feat(text-field): add multiline and auto resizing

* feat(textarea): initial textarea implementation

* feat(textarea): auto sizing feature added to TextField component when multiline prop is active

* feat(textarea): decoupled BaseTextArea component and BaseInput responsabilities

* feat(textarea): props collision fixed on TextFieldProps

* feat(textarea): linting issues fixed, test coverage for BaseTextArea added

* feat(textarea): textfield tests updated for multiline component

* feat(textarea): textfield indentation fixed

* chore: use correct ref name (input -> textarea)

* refactor: separate textarea and input interfaces

* refactor: use isomorphic layoutEffect

* style: use consistent knob casing; story naming

* chore: add multiline knob to default story

* chore: remove useless conditional

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>

* docs: initial badge and tag documentation

* docs: initial badge documentation

Added new documentation file for badge component

* docs: initial badge documentation

Added new documentation file for tag component

* chore: update formatting

* chore: fix admonitions syntax

https://docusaurus.io/docs/markdown-features/admonitions

* chore: use design system callouts

* chore: add initial anatomy descriptions

* chore: add badge and tag to sidebar

* chore: remove icon

* chore: simplify badge description

* chore: add badge to components root

* chore: add badge to public exports

* docs: add initial badge React docs

* chore: add badge to public exports

* chore: export badge and tag types

* docs(tag): add react api

* chore: update tag description and remove icon from anatomy

* chore: add tag to components landing page

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>

* docs(text-field): update section on multiline

* Update text-field.mdx

Updated the multi-line text field documentation and added cautions for autosize multi-line text fields.

* chore: reformat to remove trailing whitespace

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>

* refactor(core): remove default selection styling

* fix(core): removed default selection styling

* fix(website): changed default value null for selection-background-color and selection-text-color

* chore(deps-dev): update conventional-changelog-conventionalcommits

* chore(deps-dev): add lerna to help with releases

* chore: add codecov config

* chore(deps): yarn upgrade

* 0.0.1

* v1.3.0

* chore: update contributors

* docs(website): fix pagination display

* fix(core): make sass@1.33.x an optional peer dependency

if users are using our Sass directly, they'll need the correct version because of
https://sass-lang.com/documentation/breaking-changes/slash-div

* fix(core): prevent duplicate dependency errors on customization

this change ensures that popper/index.scss isn't used multiple times when
a developer configures it using "with".
SassError: This module was already loaded, so it can't be configured using "with".

* fix(core): make all component tokens configurable using "with"

* chore: remove root version

* chore(website): fix feature card media caps

* fix(textfield): allow the Textfield multiline component to expand vertically and horizontally

* chore(deps-dev): yarn upgrade

* v1.3.1

* Create table.mdx

Added usage documentation for the table component.

* style: format with prettier

* chore: use callouts for dos and don'ts

* chore: update specification

* chore: add table to sidebar and index

* chore: tighten up the sort indicator description

Co-authored-by: Evan Yamanishi <yamanishi1@gmail.com>
Co-authored-by: Anand Patil <apatil@wwnorton.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Yamanishi <sh0ji@users.noreply.github.com>
Co-authored-by: JRebella <47323512+JRebella@users.noreply.github.com>
Co-authored-by: wguevara-norton <85630200+wguevara-norton@users.noreply.github.com>
Co-authored-by: Wilmer Guevara <wguevara@wwnorton.com>
@devinrhode2
Copy link

I think EventTarget could at least have a few basic dom properties, no? Could it be typed to HTMLElement or at least Element?

@hontas
Copy link

hontas commented Oct 4, 2023

Just ran into an issue with this. Our case is that we have a handler higher up in the DOM hierarchy that should perform an action if the event was not dispatched on a child of an element that has a certain class.

Our code is roughly as follows:

const onArticleClick = (event: React.MouseEvent) => {
  if (event.target.closest('.prevent-on-article-click-handler')) {
    return false
  }
  // do things...
} 

Looking at the documentation in MDN for target and currentTarget I can't see why target cannot be considered an Element for a click event?

We could of course use another approach and call event.stopPropagation() instead but there's nothing wrong with the implementation, and since target is not typed as an Element we are forced to cast it or use any.

The reasoning above by @bbenezech has it's points but it's not contradicting that target could/should default to Element, even if a user that did not know the difference was using the "wrong" one.

Happy for some feedback if I misunderstood or have reached some wrong conclusion.

The way this seems to be intended at helping users use the mostly correct property is causing us issues because the types are not mirroring what the actual DOM is giving us, and I think that typing to help rather than typing to be correct may be the wrong approach here.

I'm not proposing that target should be typed as EventTarget & T rather that it be type as EventTarget & Element (since it makes no sense having it as generic) or at least be passed down from the specific event where it makes sense that it should be an Element

@carl0s-sa
Copy link

I'm not proposing that target should be typed as EventTarget & T rather that it be type as EventTarget & Element (since it makes no sense having it as generic) or at least be passed down from the specific event where it makes sense that it should be an Element

Any plans to make this happen? Just ran into @hontas's case. I can add a PR for it if there's none already

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants