143

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)
    }}
  />
)
Justin Levi Winter
  • 2,327
  • 2
  • 17
  • 30

11 Answers11

209

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

Tyler Sebastian
  • 9,067
  • 6
  • 39
  • 62
  • 1
    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. – Justin Levi Winter Jul 14 '17 at 13:36
  • 1
    Lambdas are forbidden in JSX attributes due to their rendering performance impact – Alexey Sh. Aug 10 '18 at 18:22
  • 1
    @AlexeySh. they're discouraged, yes. I copied from OP's code. – Tyler Sebastian Sep 25 '18 at 19:30
  • 1
    This could trivially be moved into a `React.useCallback` to save on the lambda – Patrick Sep 13 '19 at 13:09
  • Why is the reply in your edit the proper way? What's wrong with your approach? – Tomiwa Oct 29 '21 at 22:06
39

React.MouseEvent works for me:

private onClick = (e: React.MouseEvent<HTMLInputElement>) => {
  let button = e.target as HTMLInputElement;
}
Dawied
  • 897
  • 10
  • 16
24

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.

Justin Levi Winter
  • 2,327
  • 2
  • 17
  • 30
16

You can do like this

   onClick: React.MouseEventHandler<HtmlInputElement> = (e) => {
     const button = e.target as HTML
}
Ranjan Kumar
  • 321
  • 3
  • 4
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

insomniac2846
  • 442
  • 5
  • 8
8

I think the correct type is

event:React.MouseEvent<HTMLButtonElement, MouseEvent>
  • 1
    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! – GiselleMtnezS Feb 16 '23 at 17:10
6

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

0

I think you may try. It works on me.

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

<Button onClick={handleClick}>Click me</button>
Ruby
  • 1
  • 2
0
onClick={(event: Event) => {
    const { index } = event.target as unknown as { index: number }; 
    makeMove(ownMark, index);
}}
LEMUEL ADANE
  • 8,336
  • 16
  • 58
  • 72
0
const Example = () => {

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

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

So the solution is React.MouseEvent<HTMLButtonElement>

yakob abada
  • 1,101
  • 14
  • 20