React TypeScript Events and Handlers: Complete Types
Event handlers are the bridge between user actions and your React code. In TypeScript, every event handler receives a typed event object that gives you safe access to event properties. Understanding how to type events—from simple input changes to keyboard shortcuts and custom events—is essential for building interactive React components without runtime surprises.
What Event Types Does React Provide?
React provides type-safe event objects through the React namespace. The most common are ChangeEvent, MouseEvent, FormEvent, KeyboardEvent, and FocusEvent. Each event type includes all properties relevant to that interaction, ensuring your code only accesses valid properties:
Here is a reference of common event types:
| Event Type | HTML Element | Properties |
|---|---|---|
ChangeEvent<HTMLInputElement> | input, textarea | value, checked |
MouseEvent<HTMLElement> | button, div | clientX, clientY, button |
FormEvent<HTMLFormElement> | form | target, currentTarget |
KeyboardEvent<HTMLElement> | input, button, div | key, code, shiftKey |
FocusEvent<HTMLElement> | any focusable element | relatedTarget |
PointerEvent<HTMLElement> | pointer interactions | pointerId, pointerType |
How Do You Type Input Change Events?
The ChangeEvent is the most common event in React forms. It fires when an input value changes. You type it with the specific HTML element (HTMLInputElement, HTMLTextAreaElement, or HTMLSelectElement):
import { ChangeEvent, useState } from 'react';
function SearchForm() {
const [query, setQuery] = useState("");
const [category, setCategory] = useState("all");
// Type the input change event
const handleQueryChange = (event: ChangeEvent<HTMLInputElement>) => {
setQuery(event.currentTarget.value);
};
// Type the select (dropdown) change event
const handleCategoryChange = (event: ChangeEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value);
};
// Type textarea change event
const handleCommentChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
console.log("Comment length:", event.currentTarget.value.length);
};
return (
<div>
<input
type="text"
value={query}
onChange={handleQueryChange}
placeholder="Search..."
/>
<select value={category} onChange={handleCategoryChange}>
<option value="all">All Categories</option>
<option value="tech">Tech</option>
<option value="lifestyle">Lifestyle</option>
</select>
<textarea
value={query}
onChange={handleCommentChange}
placeholder="Leave a comment..."
/>
</div>
);
}
The key property is event.currentTarget.value, which contains the input's value. Use currentTarget (the element that owns the handler) rather than target (the element that triggered the event, which may be a child).
How Do You Type Mouse Events?
Mouse events like click, mouseenter, and mouseleave use the MouseEvent type. The generic parameter specifies which HTML element the handler is attached to:
import { MouseEvent } from 'react';
function ClickableCard() {
// Type a button click handler
const handleButtonClick = (event: MouseEvent<HTMLButtonElement>) => {
console.log("Button clicked at:", event.clientX, event.clientY);
};
// Type a div click handler
const handleDivClick = (event: MouseEvent<HTMLDivElement>) => {
if (event.button === 0) {
console.log("Left mouse button clicked");
}
};
// Mouse enter / leave
const handleMouseEnter = (event: MouseEvent<HTMLElement>) => {
console.log("Mouse entered the element");
};
return (
<div onClick={handleDivClick} onMouseEnter={handleMouseEnter}>
<h3>Click me!</h3>
<button onClick={handleButtonClick}>
Click for coordinates
</button>
</div>
);
}
Useful MouseEvent properties include clientX and clientY (cursor position), button (which button was pressed: 0 = left, 1 = middle, 2 = right), and shiftKey / ctrlKey / altKey (modifier keys).
How Do You Type Form Submit Events?
Form submit events use FormEvent. Unlike change events, form events are attached to the <form> element itself, not individual inputs. Always call event.preventDefault() to prevent the default page reload:
import { FormEvent, ChangeEvent, useState } from 'react';
interface LoginFormData {
email: string;
password: string;
}
function LoginForm() {
const [data, setData] = useState<LoginFormData>({
email: "",
password: "",
});
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.currentTarget;
setData((prev) => ({ ...prev, [name]: value }));
};
// Type the form submit event
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
// Prevent the browser from reloading the page
event.preventDefault();
// Now safely access the form and its data
console.log("Submitting:", data);
// Make an API call, etc.
};
return (
<form onSubmit={handleSubmit}>
<input
name="email"
type="email"
value={data.email}
onChange={handleInputChange}
placeholder="Email"
/>
<input
name="password"
type="password"
value={data.password}
onChange={handleInputChange}
placeholder="Password"
/>
<button type="submit">Log In</button>
</form>
);
}
The FormEvent type ensures you call preventDefault() and gives you safe access to the form element via event.currentTarget.
How Do You Type Keyboard Events?
Keyboard events fire when the user presses or releases a key. The KeyboardEvent type gives you access to key (the character or function key name) and code (the physical key code):
import { KeyboardEvent, useState } from 'react';
function SearchWithEnter() {
const [query, setQuery] = useState("");
// Type the keyboard event
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
// Execute search on Enter key
if (event.key === "Enter") {
event.preventDefault();
console.log("Searching for:", query);
}
// Clear on Escape key
if (event.key === "Escape") {
setQuery("");
}
// Check modifier keys
if (event.ctrlKey && event.key === "s") {
event.preventDefault();
console.log("Ctrl+S pressed");
}
};
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
onKeyDown={handleKeyDown}
placeholder="Type and press Enter to search..."
/>
);
}
Common keyboard events are onKeyDown, onKeyUp, and onKeyPress. Use event.key to check which key was pressed (e.g., "Enter", "Escape", "a"). Use modifier keys like event.ctrlKey, event.shiftKey, and event.altKey to detect key combinations.
How Do You Inline Event Handler Types?
For simple handlers, you can inline the type directly in the JSX without defining a separate function:
function QuickButton() {
return (
<button
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
console.log("Clicked at:", event.clientX, event.clientY);
}}
>
Click me
</button>
);
}
This approach is fine for trivial handlers but becomes unreadable for complex logic. Extract complex handlers into named functions for clarity.
Key Takeaways
- Use
ChangeEvent<HTMLInputElement>for input changes; usecurrentTarget.valueto get the value safely. - Use
MouseEvent<HTMLElement>for clicks and mouse movements; properties includeclientX,clientY, andbutton. - Use
FormEvent<HTMLFormElement>for form submissions; always callpreventDefault()to stop page reload. - Use
KeyboardEvent<HTMLElement>for keyboard interactions; checkevent.keyfor the pressed key. - Always prefer
currentTargetovertargetto access the element that owns the event handler.
Frequently Asked Questions
How do I type a custom event or event emitter pattern in React?
React does not directly support custom events in component handlers. If you need custom events, use a state management solution (like useContext + useReducer) or an event emitter library. For most React code, state updates and props are sufficient instead of custom events.
What is the difference between target and currentTarget?
target is the element that triggered the event (might be a nested child). currentTarget is the element that owns the event handler. In React, always use currentTarget because target can be unstable after event pooling. React 17+ no longer pools events, but currentTarget is the safer choice.
How do I type a handler that accepts multiple event types?
Use a union type or create a generic handler type:
type EventHandler = (event: ChangeEvent<HTMLInputElement> | KeyboardEvent<HTMLInputElement>) => void;
const handleInput: EventHandler = (event) => {
if (event.type === 'change') {
// Handle change
} else if (event.type === 'keydown') {
// Handle keydown
}
};
This is rare; it is usually cleaner to have separate handlers.
Do I need to type the event in every handler?
If a handler is simple, TypeScript often infers the type. However, it is a best practice to always annotate events explicitly for readability and to catch mistakes early. Explicit annotations also help teammates understand the expected event type.
What about pointer events and touch events?
Pointer events unify mouse, touch, and pen input:
const handlePointer = (event: PointerEvent<HTMLElement>) => {
console.log(event.pointerType); // "mouse", "touch", "pen"
};
Touch events are less common in modern React; prefer pointer events for flexibility.