DEV Community

Pierre Ouannes
Pierre Ouannes

Posted on • Originally published at devtrium.com

React Events and Typescript: a complete guide

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:

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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!

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
  console.log('Submit button clicked!');
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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!

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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!

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

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.

Discussion (0)