
React Syntax Events
- 11 installs
- 6 repo stars
- Updated July 8, 2026
- openaec-foundation/react-claude-skill-package
Helps with frontend development tasks.
About
react-syntax-events is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-syntax-events
- Frontend Development
- AI-coding skill
React Syntax Events by the numbers
- 11 all-time installs (skills.sh)
- Ranked #1,658 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-syntax-eventsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/react-claude-skill-package ↗ |
What it does
Helps with frontend development tasks.
Files
react-syntax-events
Quick Reference
Synthetic Event System
React wraps all native browser events in SyntheticEvent objects. These wrappers normalize cross-browser differences and provide a consistent API.
| Concept | Detail |
|---|---|
| Wrapper | Every handler receives a SyntheticEvent, NOT a native Event |
| Delegation | React attaches ONE listener to the root container, NOT to individual DOM nodes |
| Pooling | React 17+ does NOT pool events -- accessing e in async callbacks is safe |
| Native access | Use e.nativeEvent to access the underlying browser event |
TypeScript Event Types
| React Type | Native Equivalent | Common Elements |
|---|---|---|
React.MouseEvent<T> | MouseEvent | HTMLButtonElement, HTMLDivElement |
React.ChangeEvent<T> | Event | HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement |
React.FormEvent<T> | Event | HTMLFormElement |
React.KeyboardEvent<T> | KeyboardEvent | HTMLInputElement, HTMLDivElement |
React.FocusEvent<T> | FocusEvent | HTMLInputElement, HTMLButtonElement |
React.DragEvent<T> | DragEvent | HTMLDivElement, HTMLImageElement |
React.TouchEvent<T> | TouchEvent | HTMLDivElement, HTMLButtonElement |
React.ClipboardEvent<T> | ClipboardEvent | HTMLInputElement, HTMLDivElement |
React.WheelEvent<T> | WheelEvent | HTMLDivElement, HTMLElement |
React.PointerEvent<T> | PointerEvent | HTMLDivElement, HTMLButtonElement |
The generic parameter T specifies the element the handler is attached to. ALWAYS provide it for correct e.currentTarget typing.
Critical Warnings
NEVER use onKeyPress -- it is deprecated. ALWAYS use onKeyDown or onKeyUp instead.
NEVER type event handlers as (e: any) => void -- ALWAYS use the specific React event type with the correct element generic.
NEVER call e.stopPropagation() as a default habit -- it breaks parent listeners, analytics tools, and modal close handlers. ONLY use it when you have a specific reason.
ALWAYS call e.preventDefault() in onSubmit handlers to prevent full page reload.
ALWAYS provide the element generic parameter (e.g., <HTMLInputElement>) -- without it, e.currentTarget is typed as HTMLElement and you lose access to element-specific properties like .value.
NEVER read e.currentTarget inside async/await or setTimeout -- currentTarget is set to null after the event handler returns. Extract the value synchronously first.
---
Event Handler Typing Patterns
Inline Handler
<button onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget.disabled);
}}>
Click
</button>Extracted Handler
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
console.log(e.currentTarget.disabled);
};
<button onClick={handleClick}>Click</button>Handler as Props
interface ButtonProps {
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
onFocus?: (e: React.FocusEvent<HTMLButtonElement>) => void;
}
const Button = ({ onClick, onFocus }: ButtonProps): JSX.Element => (
<button onClick={onClick} onFocus={onFocus}>Click</button>
);React.EventHandler Type
React provides shorthand types for handler props:
interface Props {
onClick: React.MouseEventHandler<HTMLButtonElement>;
onChange: React.ChangeEventHandler<HTMLInputElement>;
onSubmit: React.FormEventHandler<HTMLFormElement>;
onKeyDown: React.KeyboardEventHandler<HTMLInputElement>;
}These are equivalent to (e: React.MouseEvent<HTMLButtonElement>) => void, etc.
---
Passing Data to Handlers
Arrow Function (Preferred)
const handleDelete = (id: string, e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation();
deleteItem(id);
};
{items.map((item) => (
<button key={item.id} onClick={(e) => handleDelete(item.id, e)}>
Delete {item.name}
</button>
))}Currying Pattern
const handleAction = (id: string) => (e: React.MouseEvent<HTMLButtonElement>): void => {
e.preventDefault();
performAction(id);
};
<button onClick={handleAction(item.id)}>Act</button>NEVER use .bind(this, arg) in function components -- it creates a new function reference every render just like arrow functions, but with worse readability.
---
stopPropagation vs preventDefault
| Method | Purpose | Use When |
|---|---|---|
e.preventDefault() | Prevents the browser default action | Form submit, link navigation, drag default behavior |
e.stopPropagation() | Stops the event from reaching parent handlers | Nested clickable elements, preventing parent dismissals |
Decision Tree
1. Is the browser doing something unwanted (page reload, navigation)? --> preventDefault() 2. Is a parent handler firing when it should not? --> stopPropagation() 3. Both? --> Call both explicitly
const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>): void => {
e.preventDefault(); // stop navigation
e.stopPropagation(); // stop parent onClick
openModal();
};---
Capture Phase
React supports capture-phase listeners by appending Capture to any event prop:
<div onClickCapture={(e: React.MouseEvent<HTMLDivElement>) => {
// Fires BEFORE child onClick handlers
console.log("Captured click on:", e.target);
}}>
<button onClick={() => console.log("Button clicked")}>
Click me
</button>
</div>Event flow order: Capture (root to target) --> Target --> Bubble (target to root)
ALWAYS use capture-phase handlers when you need to intercept events before children process them (e.g., global escape key handling, focus trapping).
---
Form Events
onChange
Fires on every keystroke for text inputs (unlike native change which fires on blur):
const [value, setValue] = useState<string>("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setValue(e.currentTarget.value);
};
<input type="text" value={value} onChange={handleChange} />onSubmit
ALWAYS call e.preventDefault() to prevent page reload:
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
submitForm(Object.fromEntries(formData));
};
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit">Submit</button>
</form>---
Mouse Events
| Prop | Fires When | Bubbles |
|---|---|---|
onClick | Click (mousedown + mouseup) | Yes |
onDoubleClick | Double click | Yes |
onMouseDown / onMouseUp | Press / release | Yes |
onMouseEnter | Pointer enters element | No |
onMouseLeave | Pointer leaves element | No |
onMouseOver / onMouseOut | Pointer enters/leaves (includes children) | Yes |
onContextMenu | Right-click | Yes |
onMouseEnter / onMouseLeave do NOT bubble -- they fire only for the exact element, not its children. Use these for hover states. Use onMouseOver / onMouseOut when you need bubbling behavior.
---
Keyboard Events
| Prop | Fires When |
|---|---|
onKeyDown | Key pressed down (repeats while held) |
onKeyUp | Key released |
NEVER use onKeyPress -- it is deprecated and does not fire for non-character keys (Escape, Arrow keys, etc.).
Key Detection
ALWAYS use e.key (string value) instead of e.keyCode (deprecated numeric code):
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.key === "Enter") {
submitSearch();
}
if (e.key === "Escape") {
clearInput();
}
};Modifier Keys
const handleShortcut = (e: React.KeyboardEvent<HTMLDivElement>): void => {
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
e.preventDefault(); // prevent browser save dialog
saveDocument();
}
};| Property | Key |
|---|---|
e.ctrlKey | Ctrl (Windows/Linux) |
e.metaKey | Cmd (macOS) / Win (Windows) |
e.shiftKey | Shift |
e.altKey | Alt / Option |
---
Focus Events
| Prop | Fires When | Bubbles |
|---|---|---|
onFocus | Element gains focus | Yes (in React) |
onBlur | Element loses focus | Yes (in React) |
React's onFocus and onBlur bubble, unlike the native focus/blur events. This matches the native focusin/focusout behavior.
relatedTarget
const handleBlur = (e: React.FocusEvent<HTMLInputElement>): void => {
if (e.relatedTarget === null) {
// Focus left the document entirely
validateField();
}
};e.relatedTarget is the element that received (onBlur) or lost (onFocus) focus. It is null when focus moves outside the document.
---
React 18 vs React 19 Differences
| Feature | React 18 | React 19 |
|---|---|---|
| Event delegation | Root container | Root container (unchanged) |
| Event pooling | Removed (since 17) | Removed |
ref as prop | Use forwardRef for function components | ref is a regular prop -- no forwardRef needed |
| Form actions | Not available | <form action={fn}> with useActionState |
In React 19, forms can use the action prop directly. For new projects on React 19, prefer action + useActionState over manual onSubmit + preventDefault for form submissions with server mutations.
---
Reference Links
- references/examples.md -- Complete event handling examples with TypeScript
- references/api-table.md -- All synthetic event types with properties and TypeScript signatures
- references/anti-patterns.md -- Common event handling mistakes and corrections
Official Sources
- https://react.dev/learn/responding-to-events
- https://react.dev/reference/react-dom/components/common#event-handler
- https://react.dev/learn/typescript#typing-dom-events
- https://react.dev/reference/react-dom/components/input
- https://react.dev/reference/react-dom/components/form
Event Handling Anti-Patterns
Common mistakes in React event handling with corrections.
---
AP-001: Using any for Event Types
Wrong
const handleClick = (e: any) => {
console.log(e.currentTarget.value); // No type safety
};Correct
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
console.log(e.currentTarget.disabled); // Full autocomplete and type checking
};Why: Using any defeats TypeScript's purpose. You lose autocomplete, miss typos, and allow impossible property access (e.g., .value on a button).
---
AP-002: Using onKeyPress (Deprecated)
Wrong
<input onKeyPress={(e) => {
if (e.key === "Escape") { /* NEVER fires -- Escape is not a character key */ }
}} />Correct
<input onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") { clearInput(); }
}} />Why: onKeyPress is deprecated, does not fire for non-character keys (Escape, Arrow, Tab, etc.), and will be removed in a future browser version.
---
AP-003: Using keyCode Instead of key
Wrong
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.keyCode === 13) { submit(); } // magic number, deprecated
};Correct
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.key === "Enter") { submit(); } // readable, standard
};Why: keyCode is deprecated, requires memorizing numeric codes, and varies across keyboard layouts. e.key returns a human-readable string.
---
AP-004: Forgetting preventDefault on Form Submit
Wrong
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
// Missing e.preventDefault() -- causes full page reload
submitData();
};Correct
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
submitData();
};Why: Without preventDefault(), the browser performs a full form submission (GET/POST request + page reload), destroying React state.
---
AP-005: Missing Element Generic Parameter
Wrong
const handleChange = (e: React.ChangeEvent): void => {
console.log(e.currentTarget.value); // Error: Property 'value' does not exist on type 'HTMLElement'
};Correct
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
console.log(e.currentTarget.value); // OK: HTMLInputElement has .value
};Why: Without the generic parameter, currentTarget is typed as HTMLElement, which does not have .value, .checked, .files, or other element-specific properties.
---
AP-006: Reading currentTarget in Async Code
Wrong
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
await someAsyncOperation();
console.log(e.currentTarget.id); // currentTarget is null after handler returns
};Correct
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
const buttonId = e.currentTarget.id; // Extract value synchronously
await someAsyncOperation();
console.log(buttonId); // Use the extracted value
};Why: React sets currentTarget to null after the synchronous event handler completes. Any access after an await or in a setTimeout callback reads null.
---
AP-007: Overusing stopPropagation
Wrong
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation(); // "Just in case" -- breaks everything above
doSomething();
};Correct
// Only use stopPropagation when a parent handler would cause incorrect behavior
const handleInnerClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation(); // Prevents parent's onClick from closing the modal
selectItem();
};Why: stopPropagation() prevents ALL parent handlers from receiving the event. This breaks analytics event listeners, modal backdrop close handlers, dropdown close-on-outside-click patterns, and any other parent-level event logic. ONLY use it when you have identified a specific parent handler that must not fire.
---
AP-008: Using .bind() in Function Components
Wrong
const Component = ({ items }: Props): JSX.Element => (
<ul>
{items.map((item) => (
<li key={item.id}>
<button onClick={handleDelete.bind(null, item.id)}>Delete</button>
</li>
))}
</ul>
);Correct
const Component = ({ items }: Props): JSX.Element => (
<ul>
{items.map((item) => (
<li key={item.id}>
<button onClick={() => handleDelete(item.id)}>Delete</button>
</li>
))}
</ul>
);Why: .bind() creates a new function every render (same as arrow functions) but is harder to read and loses TypeScript type inference for the bound arguments. In function components, arrow functions are the standard pattern.
---
AP-009: Calling the Handler Instead of Passing It
Wrong
<button onClick={handleClick()}>Click</button>
// handleClick() is CALLED during render, not on clickCorrect
<button onClick={handleClick}>Click</button>
// or with arguments:
<button onClick={() => handleClick(id)}>Click</button>Why: onClick={handleClick()} executes handleClick immediately during render and passes its return value (usually undefined) as the event handler. This causes the handler to fire on every render, not on click.
---
AP-010: Wrong Event Type for onChange on Input
Wrong
const handleChange = (e: React.InputEvent<HTMLInputElement>): void => {
// React.InputEvent does not exist in React's type definitions
};Correct
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setValue(e.currentTarget.value);
};Why: React does not expose InputEvent as a synthetic event type. The onChange handler in React uses React.ChangeEvent, which fires on every keystroke (unlike the native change event that fires on blur).
---
AP-011: Using e.target Instead of e.currentTarget for Value Access
Wrong
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setValue((e.target as HTMLInputElement).value); // requires type assertion
};Correct
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setValue(e.currentTarget.value); // already typed as HTMLInputElement
};Why: e.target is typed as EventTarget (could be any child element). e.currentTarget is typed with the generic parameter you specified, giving correct types without assertions. Use e.target only when you specifically need the element that triggered the event (which may be a child).
---
AP-012: Forgetting preventDefault on DragOver
Wrong
<div
onDragOver={(e: React.DragEvent<HTMLDivElement>) => {
setIsDragOver(true);
// Missing e.preventDefault() -- drop will NOT work
}}
onDrop={(e: React.DragEvent<HTMLDivElement>) => {
handleFiles(e.dataTransfer.files); // onDrop never fires
}}
/>Correct
<div
onDragOver={(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault(); // REQUIRED to make the element a valid drop target
setIsDragOver(true);
}}
onDrop={(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
handleFiles(e.dataTransfer.files);
}}
/>Why: The browser default behavior for dragover is to reject the drop. You MUST call preventDefault() on dragover to signal that the element accepts drops. Without it, the onDrop handler will never fire.
Synthetic Event Types -- Complete API Reference
All React synthetic event types with their properties and TypeScript signatures.
---
Base: React.SyntheticEvent\<T\>
All React events extend SyntheticEvent. Properties available on EVERY event:
| Property | Type | Description |
|---|---|---|
bubbles | boolean | Whether the event bubbles |
cancelable | boolean | Whether the event can be canceled |
currentTarget | T | The element the handler is attached to (typed by generic) |
target | EventTarget | The element that triggered the event (may be a child) |
defaultPrevented | boolean | Whether preventDefault() was called |
eventPhase | number | Current phase (0=none, 1=capture, 2=target, 3=bubble) |
isTrusted | boolean | Whether the event was triggered by user action |
nativeEvent | Event | The underlying native browser event |
timeStamp | number | Timestamp when the event was created |
type | string | The event type name (e.g., "click", "change") |
preventDefault() | void | Prevent the browser default action |
stopPropagation() | void | Stop the event from bubbling to parent handlers |
isPropagationStopped() | boolean | Whether stopPropagation() was called |
isDefaultPrevented() | boolean | Whether preventDefault() was called |
---
React.MouseEvent\<T\>
Handler type: React.MouseEventHandler<T>
| Property | Type | Description |
|---|---|---|
altKey | boolean | Alt key pressed during click |
button | number | Which button (0=left, 1=middle, 2=right) |
buttons | number | Bitmask of pressed buttons |
clientX / clientY | number | Coordinates relative to viewport |
ctrlKey | boolean | Ctrl key pressed |
metaKey | boolean | Meta/Cmd key pressed |
movementX / movementY | number | Movement since last mousemove |
pageX / pageY | number | Coordinates relative to document |
screenX / screenY | number | Coordinates relative to screen |
shiftKey | boolean | Shift key pressed |
relatedTarget | `EventTarget \ | null` |
getModifierState(key) | boolean | Check modifier key state |
Mouse Event Props
| Prop | Bubbles | Description |
|---|---|---|
onClick | Yes | Click (mousedown + mouseup on same element) |
onDoubleClick | Yes | Double click |
onMouseDown | Yes | Mouse button pressed |
onMouseUp | Yes | Mouse button released |
onMouseEnter | No | Pointer enters element (not children) |
onMouseLeave | No | Pointer leaves element (not children) |
onMouseOver | Yes | Pointer enters element or children |
onMouseOut | Yes | Pointer leaves element or children |
onMouseMove | Yes | Pointer moves over element |
onContextMenu | Yes | Right-click / context menu trigger |
---
React.KeyboardEvent\<T\>
Handler type: React.KeyboardEventHandler<T>
| Property | Type | Description |
|---|---|---|
key | string | The key value ("Enter", "Escape", "a", "ArrowDown") |
code | string | Physical key code ("KeyA", "Enter", "ArrowDown") |
altKey | boolean | Alt key pressed |
ctrlKey | boolean | Ctrl key pressed |
metaKey | boolean | Meta/Cmd key pressed |
shiftKey | boolean | Shift key pressed |
repeat | boolean | Whether the key is being held down (auto-repeat) |
locale | string | Locale string |
location | number | Key location (0=standard, 1=left, 2=right, 3=numpad) |
getModifierState(key) | boolean | Check modifier key state |
Keyboard Event Props
| Prop | Description |
|---|---|
onKeyDown | Key pressed (fires repeatedly when held) |
onKeyUp | Key released |
NEVER use onKeyPress -- deprecated, does not fire for non-character keys.
---
React.ChangeEvent\<T\>
Handler type: React.ChangeEventHandler<T>
No additional properties beyond SyntheticEvent. The value is accessed via e.currentTarget.value.
Common Element Types
| Element | Generic Parameter | Access Value Via |
|---|---|---|
| Text input | HTMLInputElement | e.currentTarget.value |
| Checkbox | HTMLInputElement | e.currentTarget.checked |
| Radio | HTMLInputElement | e.currentTarget.value |
| Select | HTMLSelectElement | e.currentTarget.value |
| Textarea | HTMLTextAreaElement | e.currentTarget.value |
| File input | HTMLInputElement | e.currentTarget.files |
---
React.FormEvent\<T\>
Handler type: React.FormEventHandler<T>
No additional properties beyond SyntheticEvent. Typically used with HTMLFormElement.
Form Event Props
| Prop | Description |
|---|---|
onSubmit | Form submitted (ALWAYS call e.preventDefault()) |
onReset | Form reset |
onInput | Value changed (fires immediately, before onChange) |
onChange | Value changed (React fires on every keystroke for inputs) |
onInvalid | Form validation failed |
---
React.FocusEvent\<T\>
Handler type: React.FocusEventHandler<T>
| Property | Type | Description |
|---|---|---|
relatedTarget | `EventTarget \ | null` |
Focus Event Props
| Prop | Bubbles | Description |
|---|---|---|
onFocus | Yes (in React) | Element received focus |
onBlur | Yes (in React) | Element lost focus |
Note: React's onFocus/onBlur bubble, matching native focusin/focusout.
---
React.DragEvent\<T\>
Handler type: React.DragEventHandler<T>
Extends MouseEvent. Additional property:
| Property | Type | Description |
|---|---|---|
dataTransfer | DataTransfer | Data being dragged (files, text, custom data) |
Drag Event Props
| Prop | Fires On |
|---|---|
onDrag | Element being dragged (continuous) |
onDragStart | Drag begins |
onDragEnd | Drag ends (on dragged element) |
onDragEnter | Dragged item enters a drop target |
onDragLeave | Dragged item leaves a drop target |
onDragOver | Dragged item is over a drop target (MUST call e.preventDefault() to allow drop) |
onDrop | Item dropped on target |
---
React.TouchEvent\<T\>
Handler type: React.TouchEventHandler<T>
| Property | Type | Description |
|---|---|---|
touches | TouchList | All current touch points |
targetTouches | TouchList | Touch points on the target element |
changedTouches | TouchList | Touch points that changed |
altKey | boolean | Alt key pressed |
ctrlKey | boolean | Ctrl key pressed |
metaKey | boolean | Meta key pressed |
shiftKey | boolean | Shift key pressed |
Touch Event Props
| Prop | Description |
|---|---|
onTouchStart | Finger touches the screen |
onTouchMove | Finger moves on the screen |
onTouchEnd | Finger leaves the screen |
onTouchCancel | Touch interrupted (e.g., system gesture) |
---
React.ClipboardEvent\<T\>
Handler type: React.ClipboardEventHandler<T>
| Property | Type | Description |
|---|---|---|
clipboardData | DataTransfer | Clipboard content |
Clipboard Event Props
| Prop | Description |
|---|---|
onCopy | Content copied |
onCut | Content cut |
onPaste | Content pasted |
---
React.WheelEvent\<T\>
Handler type: React.WheelEventHandler<T>
Extends MouseEvent. Additional properties:
| Property | Type | Description |
|---|---|---|
deltaX | number | Horizontal scroll amount |
deltaY | number | Vertical scroll amount |
deltaZ | number | Z-axis scroll amount |
deltaMode | number | Unit (0=pixels, 1=lines, 2=pages) |
Wheel Event Props
| Prop | Description |
|---|---|
onWheel | Mouse wheel scrolled |
---
React.PointerEvent\<T\>
Handler type: React.PointerEventHandler<T>
Extends MouseEvent. Unifies mouse, touch, and stylus input. Additional properties:
| Property | Type | Description |
|---|---|---|
pointerId | number | Unique pointer identifier |
pointerType | string | Input type: "mouse", "pen", "touch" |
width / height | number | Contact geometry |
pressure | number | Pressure (0.0 to 1.0) |
tangentialPressure | number | Tangential pressure (-1.0 to 1.0) |
tiltX / tiltY | number | Tilt angle in degrees |
twist | number | Clockwise rotation (0 to 359) |
isPrimary | boolean | Whether this is the primary pointer |
Pointer Event Props
| Prop | Description |
|---|---|
onPointerDown | Pointer activated (pressed) |
onPointerUp | Pointer deactivated (released) |
onPointerMove | Pointer moved |
onPointerEnter | Pointer enters element (no bubble) |
onPointerLeave | Pointer leaves element (no bubble) |
onPointerOver | Pointer enters element (bubbles) |
onPointerOut | Pointer leaves element (bubbles) |
onPointerCancel | Pointer event canceled |
onGotPointerCapture | Element captures pointer |
onLostPointerCapture | Element loses pointer capture |
---
Handler Type Shortcuts
React provides shorthand handler types for component props:
type React.MouseEventHandler<T> = (e: React.MouseEvent<T>) => void;
type React.ChangeEventHandler<T> = (e: React.ChangeEvent<T>) => void;
type React.FormEventHandler<T> = (e: React.FormEvent<T>) => void;
type React.KeyboardEventHandler<T> = (e: React.KeyboardEvent<T>) => void;
type React.FocusEventHandler<T> = (e: React.FocusEvent<T>) => void;
type React.DragEventHandler<T> = (e: React.DragEvent<T>) => void;
type React.TouchEventHandler<T> = (e: React.TouchEvent<T>) => void;
type React.ClipboardEventHandler<T> = (e: React.ClipboardEvent<T>) => void;
type React.WheelEventHandler<T> = (e: React.WheelEvent<T>) => void;
type React.PointerEventHandler<T> = (e: React.PointerEvent<T>) => void;ALWAYS use these shorthand types when typing handler props in component interfaces.
Event Handling Examples
Complete, working TypeScript/TSX examples for React event handling patterns.
---
Controlled Text Input
import { useState } from "react";
const SearchInput = (): JSX.Element => {
const [query, setQuery] = useState<string>("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
setQuery(e.currentTarget.value);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.key === "Enter") {
e.preventDefault();
performSearch(query);
}
if (e.key === "Escape") {
setQuery("");
}
};
return (
<input
type="text"
value={query}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder="Search..."
/>
);
};---
Form Submission with Validation
import { useState } from "react";
interface FormData {
email: string;
password: string;
}
const LoginForm = (): JSX.Element => {
const [form, setForm] = useState<FormData>({ email: "", password: "" });
const [error, setError] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { name, value } = e.currentTarget;
setForm((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
if (!form.email || !form.password) {
setError("All fields are required");
return;
}
setError(null);
submitLogin(form);
};
return (
<form onSubmit={handleSubmit}>
<input
name="email"
type="email"
value={form.email}
onChange={handleChange}
/>
<input
name="password"
type="password"
value={form.password}
onChange={handleChange}
/>
{error && <p role="alert">{error}</p>}
<button type="submit">Log In</button>
</form>
);
};---
Select Element
import { useState } from "react";
type Priority = "low" | "medium" | "high";
const PrioritySelector = (): JSX.Element => {
const [priority, setPriority] = useState<Priority>("medium");
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>): void => {
setPriority(e.currentTarget.value as Priority);
};
return (
<select value={priority} onChange={handleChange}>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
);
};---
Click with Data Passing (Arrow Function)
interface Item {
id: string;
name: string;
}
interface ItemListProps {
items: Item[];
onDelete: (id: string) => void;
}
const ItemList = ({ items, onDelete }: ItemListProps): JSX.Element => (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
onDelete(item.id);
}}>
Delete
</button>
</li>
))}
</ul>
);---
Click with Data Passing (Currying)
const ItemList = ({ items, onDelete }: ItemListProps): JSX.Element => {
const handleDelete = (id: string) =>
(e: React.MouseEvent<HTMLButtonElement>): void => {
e.stopPropagation();
onDelete(id);
};
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button onClick={handleDelete(item.id)}>Delete</button>
</li>
))}
</ul>
);
};---
Keyboard Shortcuts
import { useEffect, useCallback } from "react";
const useKeyboardShortcut = (
key: string,
callback: () => void,
modifiers: { ctrl?: boolean; meta?: boolean; shift?: boolean } = {}
): void => {
const handleKeyDown = useCallback(
(e: KeyboardEvent): void => {
const ctrlMatch = modifiers.ctrl ? e.ctrlKey : true;
const metaMatch = modifiers.meta ? e.metaKey : true;
const shiftMatch = modifiers.shift ? e.shiftKey : true;
if (e.key === key && ctrlMatch && metaMatch && shiftMatch) {
e.preventDefault();
callback();
}
},
[key, callback, modifiers]
);
useEffect(() => {
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [handleKeyDown]);
};
// Usage:
// useKeyboardShortcut("s", saveDocument, { ctrl: true });---
Mouse Hover State
import { useState } from "react";
const HoverCard = ({ children }: { children: React.ReactNode }): JSX.Element => {
const [isHovered, setIsHovered] = useState<boolean>(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
transform: isHovered ? "scale(1.02)" : "scale(1)",
transition: "transform 150ms ease",
}}
>
{children}
</div>
);
};---
Focus Management
import { useState, useRef } from "react";
const EditableLabel = ({ initialValue }: { initialValue: string }): JSX.Element => {
const [isEditing, setIsEditing] = useState<boolean>(false);
const [value, setValue] = useState<string>(initialValue);
const inputRef = useRef<HTMLInputElement>(null);
const startEditing = (): void => {
setIsEditing(true);
// Focus after React re-renders with the input visible
setTimeout(() => inputRef.current?.focus(), 0);
};
const handleBlur = (e: React.FocusEvent<HTMLInputElement>): void => {
setIsEditing(false);
saveValue(value);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.key === "Enter") {
e.currentTarget.blur(); // triggers handleBlur
}
if (e.key === "Escape") {
setValue(initialValue);
setIsEditing(false);
}
};
if (!isEditing) {
return <span onClick={startEditing}>{value}</span>;
}
return (
<input
ref={inputRef}
value={value}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.currentTarget.value)}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
/>
);
};---
Drag and Drop
import { useState } from "react";
const DragDropZone = (): JSX.Element => {
const [isDragOver, setIsDragOver] = useState<boolean>(false);
const handleDragOver = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault(); // REQUIRED to allow drop
setIsDragOver(true);
};
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>): void => {
setIsDragOver(false);
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>): void => {
e.preventDefault();
setIsDragOver(false);
const files = Array.from(e.dataTransfer.files);
handleFiles(files);
};
return (
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
style={{
border: isDragOver ? "2px solid blue" : "2px dashed gray",
padding: "2rem",
}}
>
Drop files here
</div>
);
};---
Context Menu (Right-Click)
import { useState } from "react";
interface Position {
x: number;
y: number;
}
const CustomContextMenu = (): JSX.Element => {
const [menuPosition, setMenuPosition] = useState<Position | null>(null);
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>): void => {
e.preventDefault(); // prevent native context menu
setMenuPosition({ x: e.clientX, y: e.clientY });
};
const handleClick = (): void => {
setMenuPosition(null); // close menu on any click
};
return (
<div onContextMenu={handleContextMenu} onClick={handleClick}>
Right-click for options
{menuPosition && (
<ul
style={{
position: "fixed",
top: menuPosition.y,
left: menuPosition.x,
}}
>
<li onClick={() => handleAction("copy")}>Copy</li>
<li onClick={() => handleAction("paste")}>Paste</li>
<li onClick={() => handleAction("delete")}>Delete</li>
</ul>
)}
</div>
);
};---
Capture Phase: Focus Trapping
const FocusTrap = ({ children }: { children: React.ReactNode }): JSX.Element => {
const handleFocusCapture = (e: React.FocusEvent<HTMLDivElement>): void => {
// Fires BEFORE children receive focus
// Use to validate or redirect focus
const container = e.currentTarget;
if (!container.contains(e.target as Node)) {
// Focus is moving outside the trap -- redirect it back
const firstFocusable = container.querySelector<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
firstFocusable?.focus();
e.stopPropagation();
}
};
return (
<div onFocusCapture={handleFocusCapture}>
{children}
</div>
);
};---
React 19: Form Actions
// React 19 ONLY -- uses the action prop and useActionState
import { useActionState } from "react";
interface FormState {
message: string;
success: boolean;
}
async function submitAction(
prevState: FormState,
formData: FormData
): Promise<FormState> {
const email = formData.get("email") as string;
// Perform async submission
return { message: "Submitted successfully", success: true };
}
const ContactForm = (): JSX.Element => {
const [state, formAction, isPending] = useActionState(submitAction, {
message: "",
success: false,
});
return (
<form action={formAction}>
<input name="email" type="email" required disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? "Submitting..." : "Submit"}
</button>
{state.message && <p>{state.message}</p>}
</form>
);
};