Specifying onClick event type with Typescript and React.Konva
Asked Answered
E

12

159

I'm trying to get rid of my tslint error Type declaration of 'any' loses type-safety. but I'm struggling to figure out what the correct type would be for the Event.

I'm working through the Lynda "Building and Deploying a Full-Stack React Application" while trying to convert it to Typescript.

Here are the specific lines that are causing the issue:

onClick={(event: any) => {
 makeMove(ownMark, event.target.index)
}}

I have tried to declare the event as a few different types, like React.MouseEvent<HTMLElement>, plus a few other subtypes on HTMLElement, with no success as the target.index is not a property on any type I can come up with. I can see from the inspector that the currentTarget is Konva.Text and the index is set to 0 but not sure that helps me as I can't set the type to Konva.Text, which would make sense to me, but that doesn't work either.

React Konva Event target Index

Here is my full React functional component:

export const Squares = ({units, coordinates, gameState, win, gameOver, yourTurn, ownMark, move}: SquaresProps) => {
  let squares = coordinates.map( (position: number, index: number) => { 
    let makeMove = move
    let mark = gameState[index] !== 'z' ? gameState[index] : false
    let fill = 'black'

    // when someone wins you want the square to turn green
    if (win && win.includes(index)) {
      fill = 'lightGreen'
    }

    if (gameOver || !yourTurn || mark) {
      makeMove = () => console.log('nope!')
    }

    return (
      <Text
        key={index}
        x={position[0]}
        y={position[1]}
        fontSize={units}
        width={units}
        text={mark}
        fill={fill}
        fontFamily={'Helvetica'}
        aligh={'center'}
        onClick={(event: any) => {
          makeMove(ownMark, event.target.index)
        }}
      />
    )
  })

  return (
    <Layer>
      {squares}
    </Layer>
  )
}

Here are my package.json dependencies:

  "dependencies": {
    "konva": "^1.6.3",
    "material-ui": "^0.18.4",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-konva": "^1.1.3",
    "react-router": "~3.0.0",
    "react-tap-event-plugin": "^2.0.1",
    "styled-components": "^2.1.0"
  },

I think the index is being added by the Konva Layer class but I'm pretty new to the whole react ecosystem so still trying to wrap my brain around it all.

UPDATE:

I was able use declaration merging suggestion by Tyler Sebastion to define the index on the target which silenced tslint. I'm not sure this is the best approach though as it feels a bit fragile to me.

Here is the additional interface code and updated onclick event:

interface KonvaTextEventTarget extends EventTarget {
  index: number
}

interface KonvaMouseEvent extends React.MouseEvent<HTMLElement> {
  target: KonvaTextEventTarget
}

...

return (
  <Text
    key={index}
    x={position[0]}
    y={position[1]}
    fontSize={units}
    width={units}
    text={mark}
    fill={fill}
    fontFamily={'Helvetica'}
    aligh={'center'}
    onClick={(event: KonvaMouseEvent) => {
      makeMove(ownMark, event.target.index)
    }}
  />
)
Eindhoven answered 13/7, 2017 at 19:55 Comment(0)
E
30

As posted in my update above, a potential solution would be to use Declaration Merging as suggested by @Tyler-sebastion. I was able to define two additional interfaces and add the index property on the EventTarget in this way.

interface KonvaTextEventTarget extends EventTarget {
  index: number
}

interface KonvaMouseEvent extends React.MouseEvent<HTMLElement> {
  target: KonvaTextEventTarget
}

I then can declare the event as KonvaMouseEvent in my onclick MouseEventHandler function.

onClick={(event: KonvaMouseEvent) => {
          makeMove(ownMark, event.target.index)
}}

I'm still not 100% if this is the best approach as it feels a bit Kludgy and overly verbose just to get past the tslint error.

Eindhoven answered 14/7, 2017 at 15:37 Comment(0)
B
221

You're probably out of luck without some hack-y workarounds

You could try

onClick={(event: React.MouseEvent<HTMLElement>) => {
 makeMove(ownMark, (event.target as any).index)
}}

I'm not sure how strict your linter is - that might shut it up just a little bit

I played around with it for a bit, and couldn't figure it out, but you can also look into writing your own augmented definitions: https://www.typescriptlang.org/docs/handbook/declaration-merging.html

edit: please use the implementation in this reply it is the proper way to solve this issue (and also upvote him, while you're at it).

Birgitbirgitta answered 13/7, 2017 at 23:24 Comment(5)
Thanks for the idea. I was going to try and cast the target but never got around to trying. I'll definitely give that a shot although that definitely feels a bit of a workaround. I will definitely read up on the declaration merging as I haven't seen that before.Eindhoven
Lambdas are forbidden in JSX attributes due to their rendering performance impactGirard
@AlexeySh. they're discouraged, yes. I copied from OP's code.Birgitbirgitta
This could trivially be moved into a React.useCallback to save on the lambdaZero
Why is the reply in your edit the proper way? What's wrong with your approach?Argufy
A
45

React.MouseEvent works for me:

private onClick = (e: React.MouseEvent<HTMLInputElement>) => {
  let button = e.target as HTMLInputElement;
}
Aurelie answered 15/3, 2018 at 21:23 Comment(0)
E
30

As posted in my update above, a potential solution would be to use Declaration Merging as suggested by @Tyler-sebastion. I was able to define two additional interfaces and add the index property on the EventTarget in this way.

interface KonvaTextEventTarget extends EventTarget {
  index: number
}

interface KonvaMouseEvent extends React.MouseEvent<HTMLElement> {
  target: KonvaTextEventTarget
}

I then can declare the event as KonvaMouseEvent in my onclick MouseEventHandler function.

onClick={(event: KonvaMouseEvent) => {
          makeMove(ownMark, event.target.index)
}}

I'm still not 100% if this is the best approach as it feels a bit Kludgy and overly verbose just to get past the tslint error.

Eindhoven answered 14/7, 2017 at 15:37 Comment(0)
H
17

You can do like this

   onClick: React.MouseEventHandler<HtmlInputElement> = (e) => {
     const button = e.target as HTML
}
Hyperploid answered 10/6, 2021 at 2:46 Comment(1)
Could you give more details on why this should solve the problem?Clove
O
12

I think the correct type is

event:React.MouseEvent<HTMLButtonElement, MouseEvent>
Orthostichy answered 18/11, 2022 at 3:9 Comment(2)
Thank you! All of the other answers felt complex or weird, but this one is straight forward and works without issues for the click event!Orvie
Correct Type! Thanks!.Prize
R
11

You should be using event.currentTarget. React is mirroring the difference between currentTarget (element the event is attached to) and target (the element the event is currently happening on). Since this is a mouse event, type-wise the two could be different, even if it doesn't make sense for a click.

https://github.com/facebook/react/issues/5733 https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

Raff answered 11/12, 2019 at 3:49 Comment(0)
U
8

You can use IntelliSense (or equivalent) from your IDE/editor to determine the type. Temporarily place the event handler in-line with the HTML element then hover your cursor over the event. Eg.

<button onClick={(event => console.log(event))}>Click me</button>

Hovering over event in VSCode shows:

(parameter) event: React.MouseEvent<HTMLButtonElement, MouseEvent>
Urbannai answered 19/10, 2022 at 8:33 Comment(1)
You learned me how to catch fish instead of addressed me to fish market, thanks.Trilateral
A
4

Taken from the ReactKonvaCore.d.ts file:

onClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;

So, I'd say your event type is Konva.KonvaEventObject<MouseEvent>

Amidst answered 19/3, 2020 at 23:38 Comment(2)
ReactKonvaCore.d.ts => can you post direct linkZicarelli
@Nelles Quick search turned up github.com/konvajs/react-konva/blob/master/ReactKonvaCore.d.tsJinnyjinrikisha
P
2

I think you may try. It works on me.

const handleClick = (event: SyntheticEvent) => {
    const target = event.target 
};

<Button onClick={handleClick}>Click me</button>
Presume answered 16/12, 2022 at 8:30 Comment(0)
C
1
const Example = () => {

      const handleSubmission = (event: React.MouseEvent<HTMLButtonElement>): void => {
          event.preventDefault()
          .....
      }

      return <button onClick={handleSubmission}>Submit</button>
}

So the solution is React.MouseEvent<HTMLButtonElement>

Coke answered 10/4, 2023 at 9:28 Comment(0)
G
0
onClick={(event: Event) => {
    const { index } = event.target as unknown as { index: number }; 
    makeMove(ownMark, index);
}}
Gorges answered 29/1, 2023 at 10:49 Comment(0)
C
0

Also, for working with DOM you can use MouseEvent.
It will be correct for @typescript-eslint. Example:

const onClickEverywhere = (e: MouseEvent) => {
  e.preventDefault();
  e.stopPropagation();
  console.log(e.target);
};

useEffect(() => {
  document.body.addEventListener('click', onClickEverywhere);
  return document.body.removeEventListener('click', onClickEverywhere);
}, []);
Commutator answered 13/12, 2023 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.