181

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)
    }}
  />
)

12 Answers 12

245

Actual event type depends on the HTML element class originating the event.

If it's <input type=button .. it will be event: React.MouseEvent<HTMLInputElement> If it's <button .. it will be event: React.MouseEvent<HTMLButtonElement> If it's some link, or anchor (Like Floating Action Buttons), it may be event: React.MouseEvent<HTMLAnchorElement>

However, these are all subclasses of HTMLElement

So in most cases if you don't care about specific subclass of event generating element, and just want to get .ownerDocument from it or some other generic attribute from base HTMLElement, you could get away with:

onClick={(event: React.MouseEvent<HTMLElement>) => {
  // say you are served inside the iframe and want to grab right 
  // parent document reference:
  (event.target as HTMLElement).ownerDocument || document).blah
}}

However, often you need value from specific HTMLElement subclass attribute, say .value from input element, then you pretty much have to specify that element class as T for MouseEvent:

onClick={(event: React.MouseEvent<HTMLInputElement>) => {
  // .value attribute exists only on HTMLInputElement,
  // not on parent class HTMLElement
  console.log('Input field value: ' + (event.target as HTMLInputElement).value)
}}

If its not clear what the HTMLElement subclass will actually be in the end (say you have 3rd party widget library) render it and see what it renders as input element / onClick event anchor or use sniffing suggestion from @Callum-Anderson below.

Sign up to request clarification or add additional context in comments.

5 Comments

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.
Lambdas are forbidden in JSX attributes due to their rendering performance impact
@AlexeySh. they're discouraged, yes. I copied from OP's code.
This could trivially be moved into a React.useCallback to save on the lambda
Why is the reply in your edit the proper way? What's wrong with your approach?
50

React.MouseEvent works for me:

private onClick = (e: React.MouseEvent<HTMLInputElement>) => {
  let button = e.target as HTMLInputElement;
}

Comments

33

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.

Comments

19

You can do like this

   onClick: React.MouseEventHandler<HtmlInputElement> = (e) => {
     const button = e.target as HTML
}

1 Comment

Could you give more details on why this should solve the problem?
13

I think the correct type is

event:React.MouseEvent<HTMLButtonElement, MouseEvent>

2 Comments

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!
Correct Type! Thanks!.
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

Comments

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>

1 Comment

You learned me how to catch fish instead of addressed me to fish market, thanks.
5

Taken from the ReactKonvaCore.d.ts file:

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

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

2 Comments

ReactKonvaCore.d.ts => can you post direct link
3

I think you may try. It works on me.

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

<Button onClick={handleClick}>Click me</button>

Comments

2
const Example = () => {

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

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

So the solution is React.MouseEvent<HTMLButtonElement>

Comments

0
onClick={(event: Event) => {
    const { index } = event.target as unknown as { index: number }; 
    makeMove(ownMark, index);
}}

Comments

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);
}, []);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.