React Events and Typescript: a complete guide
Tags: react, typescript
Published: 2021-09-04 19:07:53

Events are everywhere in React, but learning how to properly use them and their handlers with TypeScript can be surprisingly tricky. There are several ways to do it, some better than others.

In this article we'll cover all kinds of events in TypeScript: click, form, select, input, ... First we'll see how to type events on a toy example, then I'll show you how to type any event.

Let's dive in!

## Our toy example

To show how to type events in React, we'll use the following example:

```jsx
import { useState } from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (event) => {
    setInputValue(event.target.value);
  };

  const handleClick = (event) => {
    console.log('Submit button clicked!');
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
      <input value={inputValue} onChange={handleInputChange} />
      <button onClick={handleClick}>Submit</button>
    </div>
  );
}
```

It's a very simple React app with an input field and a submit button. But if you're using TypeScript with this code, it must be screaming all kinds of obscenities right now! Don't worry, we're about to see how to set it at ease.

Note that we don't really use `handleClick`'s' argument in this code, so you could just omit it and TypeScript would be happy. But I've included it anyway just to show how you would type if you'd had a use for it.

Don't worry if you'd like to know about other events than those two. This code will be used as an example, then we'll see how to type _any_ event afterwards.

## Adding in TypeScript

There are several ways to type the above code, and we'll see the 3 main ones. There are:

1. Typing the event handler argument
2. Typing the event handler itself
3. Relying on inferred types

### Typing the event

Let's start with typing the `onClick` event. This one is quite straightforward. React provides a `MouseEvent` type you can directly use!

```tsx
import { useState, MouseEvent } from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (event) => {
    setInputValue(event.target.value);
  };

  const handleClick = (event: MouseEvent) => {
    console.log('Submit button clicked!');
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
      <input value={inputValue} onChange={handleInputChange} />
      <button onClick={handleClick}>Submit</button>
    </div>
  );
}
```

The `onClick` event is actually generated by React itself: it's a [synthetic event](https://reactjs.org/docs/events.html). A synthetic event is a React wrapper around the native browser event, to always have the same API regardless of differences in browsers.

Let's move on to the `handleInputChange` function.

It's pretty similar to `handleClick`, with a significant difference. You also import a type directly from react, which this time is called `ChangeEvent`. The difference is that `ChangeEvent` is a Generic type to which you have to provide what kind of DOM element is being used.

Not sure what Generics are? [Here is TypeScript's guide to them](https://www.typescriptlang.org/docs/handbook/2/generics.html). You can think about it as a type function that accepts one or more arguments, to enable the user of the generic to customize the exact type.


The result is the following:

```tsx
import { useState, ChangeEvent, MouseEvent } from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

  // the type variable must match the DOM element emitting the
  // event, an `input` in this case
  const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value);
  };

  const handleClick = (event: MouseEvent) => {
    console.log('Submit button clicked!');
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
      <input value={inputValue} onChange={handleInputChange} />
      <button onClick={handleClick}>Submit</button>
    </div>
  );
}
```

One thing to note in the code above is that `HTMLInputElement` refers specifically to HTML's `input` tag. If we were using a `textarea`, we would be using `HTMLTextAreaElement` instead.

And there you have it! You made TypeScript happy 😁

Note that `MouseEvent` is also a Generic type, so you can restrict it if necessary. For example, let's restrict the above `MouseEvent` to specifically be a mouse event emanating from a button.

```tsx
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
  console.log('Submit button clicked!');
};
```

### Typing the event handler

Instead of typing the event itself, as we did above, we can also type the functions themselves.

It looks very similar, and it's mostly a matter of taste. I find typing the event more flexible so I tend to use the first one, but being aware of this other option is always good.

```tsx
import { useState, ChangeEventHandler, MouseEventHandler } from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

  // the type variable must match the DOM element emitting the
  // event, an `input` in this case
  const handleInputChange: ChangeEventHandler<HTMLInputElement> = (event) => {
    setInputValue(event.target.value);
  };

  const handleClick: MouseEventHandler = (event) => {
    console.log('Submit button clicked!');
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
      <input value={inputValue} onChange={handleInputChange} />
      <button onClick={handleClick}>Submit</button>
    </div>
  );
}
```

### Relying on inferred types

Lastly, you can also rely on inferred types and not type anything yourself. For this, you need to inline your callbacks, which isn't always what you want to do.

```jsx
import { useState } from 'react';

export default function App() {
  const [inputValue, setInputValue] = useState('');

  return (
    <div className="App">
      <h1>Hello World</h1>
      <input
        value={inputValue}
        onChange={(event) => setInputValue(event.target.value)}
      />
      <button onClick={(event) => console.log('Submit button clicked!')}>
        Submit
      </button>
    </div>
  );
}
```

## Other React events

Of course, there's a lot of other events than the two shown above.

A good way to find the complete list supported by React is to have a [peak at the type definitions, in the React typings source code itself](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/32314de342f2b0cd2ed0161fd94377812267b37b/types/react/v16/index.d.ts#L1167-L1316)!

### Form Events

Building forms is very common in web development. We already saw how to handle text inputs, let's now see an example [(directly taken from React's docs on forms)](https://reactjs.org/docs/forms.html#the-select-tag) of a `select`, as well as a form submit events.

```tsx
import { useState, ChangeEvent, FormEvent } from 'react';

export default function App() {
  const [selectValue, setSelectValue] = useState('coconut');

  const handleSubmit = (event: FormEvent) => {
    console.log('Form was submitted!');
  };

  const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectValue(event.target.value);
  };

  return (
    <div className="App">
      <h1>Hello World</h1>
      <form onSubmit={handleSubmit}>
        <label>
          Pick your favorite flavor:
          <select value={selectValue} onChange={handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    </div>
  );
}
```

As you can see, it looks very similar to our first example.

### Keyboard Events

Lastly, let's see an example of handling keyboard events since those are also quite common!

```tsx
import { useState, useEffect } from 'react';

export default function App() {
  const [key, setKey] = useState('');

  useEffect(() => {
    // handle what happens on key press
    const handleKeyPress = (event: KeyboardEvent) => {
      setKey(event.key);
    };

    // attach the event listener
    document.addEventListener('keydown', handleKeyPress);

    // remove the event listener
    return () => {
      document.removeEventListener('keydown', handleKeyPress);
    };
  }, [handleKeyPress]);

  return (
    <div className="App">
      <h2>Try typing on a key</h2>
      <p>Key typed: {key}</p>
    </div>
  );
}
```

Want to learn how to implement a fully functioning keyboard shortcut in your app? [Check out this article](https://devtrium.com/posts/how-keyboard-shortcut)!

## Wrap up

I hope this article clears up how to handle events with React and Typescript! As you can see, it's pretty simple once you know how to do it.