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>
);
}
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:
- Typing the event handler argument
- Typing the event handler itself
- 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>
);
}
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>
);
}
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!');
};
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!
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!
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!
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.
Top comments (1)
great explanation !