How to use 2 types of events in typescript interface

Viewed 1224

I need to create a custom rest hook that has the parameter Event, and this parameter should be able to receive 2 types of Events - keyboard and mouse. I was trying to use union of types like MouseEvent | KeyboardEvent and it is not working :/

Any suggestions? Example below

<input
  type="text"
  value={query}
  onChange={e => setQuery(e.target.value)}
  onKeyDown={e => getUserPosition(e, query, assetType)}
/>

<Button
  fullWidth
  color="secondary"
  onClick={e => getUserPosition(e, query, assetType)}
>
  Search
</Button>
const getUserPosition = (
    e: MouseEvent & KeyboardEvent, <-- here is problem
    queryuery: string,
    assetType: AssetType
  ) => {
    if (e?.key === 'Enter') {
      // do something
    }

    if (e?.type === 'click') {
      //do something
    }
  }
3 Answers

This is really an annoying and weird move from TypeScript. First you have to use the type MouseEvent | KeyboardEvent for the event since it come from an onClick callback OR an onKeyDown callback.

After you have to be sure what kind of event it is by checking for the presence of e.key for example

const getUserPosition = (e: MouseEvent | KeyboardEvent) => {
  if (e.key) {
    // it's a KeyboardEvent 
    if (e.key === "Enter") {
    // do something
    }
  } else {
    // it's a MouseEvent 
    if (e.type === "click") {
      //do something
    }
  }
};

Problem then is that you got this error:

Property 'key' does not exist on type 'MouseEvent'

You have to use a valid check to be sure that this property key is present in the object e. You can use for that "key" in e:

const getUserPosition = (e: MouseEvent | KeyboardEvent) => {
  if ("key" in e) {
    // it's a KeyboardEvent 
    if (e.key === "Enter") {
    // do something
    }
  } else {
    // it's a MouseEvent 
    if (e.type === "click") {
      //do something
    }
  }
};

let us take a look at what you have on the function definition first...:

so you want to parse one of two kinds of events in which one property may exist on one but not the other so let us look at this first approach...

/*
 * I Like your initial approach because it makes more logical sense this way,
 * only that you have to check if the property value you want to check is actually
 * in the value
 */
const getUserPosition = (e: MouseEvent | KeyboardEvent) => {
    if ('key' in e && e?.key === 'Enter') {
        // do something
    }

    if ('type' in e && e?.type === 'click') {
        //do something
    }
};

You can try next solution. Aka strategy pattern:

import React, { FC } from 'react'

type ClickHandler = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => any

const Button: FC<{ onClick: ClickHandler }> = () => <button onClick={e => e}></button>

const hasProperty = <T, P extends string>(obj: T, prop: P) => Object.prototype.hasOwnProperty.call(obj, prop);

const isKeyboard = (e: React.KeyboardEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>):
  e is React.KeyboardEvent<HTMLInputElement> => hasProperty(e, 'key')

const isMouse = (e: React.KeyboardEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>):
  e is React.KeyboardEvent<HTMLInputElement> => hasProperty(e, 'type') && e.type === 'click'

const App: FC = () => {

  function getUserPosition(e: React.MouseEvent<HTMLButtonElement, MouseEvent>): () => (query: string) => any
  function getUserPosition(e: React.KeyboardEvent<HTMLInputElement>): () => (query: string) => any
  function getUserPosition(e: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.KeyboardEvent<HTMLInputElement>) {
    return function (query: string) {
      const x = e;
      if (isKeyboard(e) && e.key === 'Enter') {

      }

      if (isMouse(e)) {
        //do something
      }

    }
  }

  return (
    <>
      <input
        onKeyDown={e => getUserPosition(e)}
      />

      <Button
        onClick={e => getUserPosition(e)}
      >
        Search
</Button>
    </>
  )
}

Playground

Related