Devtrium

React events and TypeScript: a complete guide

September 02, 20215 min read

Introduction

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.

If you just want to quickly jump to the solution, use this link.

Let's dive in!

Our toy example

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

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.

Info

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!

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> ); }
Tip

The onClick event is actually generated by React itself: it's a synthetic event. 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.

Tip

Not sure what Generics are? Here is TypeScript's guide to them. 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:

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 😁

Info

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.

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.

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.

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!

Here are the first few.

counter

You can notice that every event inherits from SyntheticEvent, which is the base event.

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) of a select, as well as a form submit events.

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!

Tip

Want to learn how to implement a fully functioning keyboard shortcut in your app? Check out this article!

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

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.

Package versions at the time of writing

Did you enjoy this article?

If so, a quick share on Twitter could really help out!