1. Introduction đ
Interactivity is at the heart of every modern web application, and event handling is how React responds to user actions like clicks, key presses, and form submissions. This tutorial covers React's event system in depth â from basic handlers to advanced patterns like propagation, delegation, and TS typing.
Information
2. What are Events? đ¤
An event is an action that occurs in the browser â a user clicking a button, typing in a field, submitting a form, or resizing a window. Event-driven programming allows applications to react to these actions as they happen.
- User-initiated: Clicks, key presses, form submissions, scrolling.
- Browser-initiated: Page load, resize, network status changes.
- Media-related: Video play, pause, or end events.
3. React Event System âī¸
React implements its own event system layered on top of native browser events. Instead of attaching listeners to individual DOM nodes, React attaches a single listener at the root of the application and manages dispatching internally for performance and consistency.
Code Snippet
function Button() {
function handleClick() {
console.log("Button was clicked!");
}
return <button onClick={handleClick}>Click Me</button>;
}Note
4. Synthetic Events đ§Ŧ
React wraps native browser events in a cross-browser wrapper called a SyntheticEvent. It has the same interface as native events (stopPropagation(), preventDefault(), etc.) but behaves consistently across all browsers.
Code Snippet
function Input() {
function handleChange(event) {
console.log(event.target.value); // SyntheticEvent
}
return <input onChange={handleChange} />;
}Important
5. Event Handlers đī¸
An event handler is a function that runs in response to a specific event. In JSX, handlers are passed as function references to camelCase event props.
Code Snippet
function SubmitButton() {
function handleSubmit() {
console.log("Form submitted!");
}
return <button onClick={handleSubmit}>Submit</button>;
}Caution
6. Binding Event Handlers đ
In function components with Hooks, this binding issues from class components don't apply. Handlers defined as regular functions or arrow functions inside the component automatically have access to the correct scope.
Code Snippet
function Toggle() {
const [isOn, setIsOn] = useState(false);
// No binding needed â closures handle scope naturally
function handleToggle() {
setIsOn(!isOn);
}
return <button onClick={handleToggle}>{isOn ? "ON" : "OFF"}</button>;
}Note
7. Passing Arguments to Event Handlers đ¨
To pass custom arguments to an event handler, wrap the call in an arrow function so it's only invoked when the event actually fires.
Code Snippet
function TodoItem({ id, text, onDelete }) {
return (
<li>
{text}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}Tip
8. Mouse Events đąī¸
| Event | Fires When |
|---|---|
| onClick | Element is clicked |
| onDoubleClick | Element is double-clicked |
| onMouseEnter / onMouseLeave | Cursor enters or leaves an element (no bubbling) |
| onMouseOver / onMouseOut | Cursor enters or leaves, including child elements (bubbles) |
| onMouseDown / onMouseUp | Mouse button pressed or released |
Code Snippet
function HoverCard() {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{isHovered ? "đ Hovering!" : "Hover over me"}
</div>
);
}9. Keyboard Events â¨ī¸
| Event | Fires When |
|---|---|
| onKeyDown | A key is pressed down |
| onKeyUp | A key is released |
| onKeyPress | A character key is pressed (deprecated in favor of onKeyDown) |
Code Snippet
function SearchInput() {
function handleKeyDown(event) {
if (event.key === "Enter") {
console.log("Search triggered!");
}
}
return <input onKeyDown={handleKeyDown} placeholder="Search..." />;
}Caution
10. Form Events đ
| Event | Fires When |
|---|---|
| onChange | An input's value changes |
| onSubmit | A form is submitted |
| onFocus / onBlur | An input gains or loses focus |
| onInvalid | A form field fails validation |
Code Snippet
function LoginForm() {
const [email, setEmail] = useState("");
function handleSubmit(event) {
event.preventDefault();
console.log("Submitting:", email);
}
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
type="email"
/>
<button type="submit">Log In</button>
</form>
);
}Important
11. Clipboard Events đ
| Event | Fires When |
|---|---|
| onCopy | Content is copied |
| onCut | Content is cut |
| onPaste | Content is pasted |
Code Snippet
function SecureInput() {
function handlePaste(event) {
event.preventDefault();
console.log("Pasting is disabled for this field.");
}
return <input onPaste={handlePaste} />;
}12. Focus Events đ¯
Code Snippet
function ValidatedInput() {
const [touched, setTouched] = useState(false);
return (
<input
onFocus={() => console.log("Input focused")}
onBlur={() => setTouched(true)}
/>
);
}Note
13. Touch Events đ
| Event | Fires When |
|---|---|
| onTouchStart | A touch point is placed on the screen |
| onTouchMove | A touch point moves |
| onTouchEnd | A touch point is removed |
Code Snippet
function SwipeCard() {
function handleTouchStart(event) {
console.log("Touch started at", event.touches[0].clientX);
}
return <div onTouchStart={handleTouchStart}>Swipe me</div>;
}Tip
14. Pointer Events đ
Pointer events unify mouse, touch, and pen input under a single, consistent API â making them the recommended choice for modern cross-device interactions.
Code Snippet
function Draggable() {
function handlePointerDown(event) {
console.log("Pointer type:", event.pointerType); // "mouse", "touch", or "pen"
}
return <div onPointerDown={handlePointerDown}>Drag me</div>;
}15. Drag and Drop Events đ¯
| Event | Fires When |
|---|---|
| onDragStart | Dragging begins |
| onDragOver | A dragged item is over a valid drop target |
| onDrop | An item is dropped |
| onDragEnd | Dragging ends |
Code Snippet
function DropZone() {
function handleDragOver(event) {
event.preventDefault(); // required to allow dropping
}
function handleDrop(event) {
event.preventDefault();
console.log("Dropped:", event.dataTransfer.getData("text"));
}
return (
<div onDragOver={handleDragOver} onDrop={handleDrop}>
Drop files here
</div>
);
}Important
16. Media Events đŦ
| Event | Fires When |
|---|---|
| onPlay / onPause | Media starts or pauses playback |
| onEnded | Playback reaches the end |
| onVolumeChange | Volume is adjusted |
| onTimeUpdate | Playback position changes |
Code Snippet
function VideoPlayer({ src }) {
return (
<video
src={src}
onPlay={() => console.log("Playing")}
onEnded={() => console.log("Finished")}
controls
/>
);
}17. Image Events đŧī¸
Code Snippet
function Avatar({ src }) {
function handleError(event) {
event.target.src = "/fallback-avatar.png";
}
return (
<img
src={src}
onLoad={() => console.log("Image loaded")}
onError={handleError}
alt="User avatar"
/>
);
}Tip
18. Animation Events đī¸
| Event | Fires When |
|---|---|
| onAnimationStart | A CSS animation begins |
| onAnimationEnd | A CSS animation completes |
| onAnimationIteration | An animation iteration completes (for looping animations) |
Code Snippet
function AnimatedBox() {
return (
<div
className="pulse-animation"
onAnimationEnd={() => console.log("Animation finished")}
/>
);
}19. Transition Events đ
Code Snippet
function FadeIn() {
return (
<div
className="fade-transition"
onTransitionEnd={() => console.log("Transition complete")}
/>
);
}Note
20. Scroll Events đ
Code Snippet
function ScrollTracker() {
function handleScroll(event) {
console.log("Scroll position:", event.target.scrollTop);
}
return (
<div onScroll={handleScroll} style={{ overflowY: "scroll", height: "300px" }}>
{/* Long scrollable content */}
</div>
);
}Warning
21. Wheel Events đĄ
Code Snippet
function ZoomableImage() {
function handleWheel(event) {
console.log("Wheel delta:", event.deltaY);
}
return <img onWheel={handleWheel} src="/chart.png" alt="Zoomable chart" />;
}Note
22. Composition Events đ
Composition events handle text input methods for languages requiring multi-keystroke character composition, such as Chinese, Japanese, and Korean IME input.
| Event | Fires When |
|---|---|
| onCompositionStart | IME composition begins |
| onCompositionUpdate | Composition text is updated |
| onCompositionEnd | Composition finishes |
Hint
23. Event Object đĻ
Every event handler receives a SyntheticEvent object containing useful information about the event, including the target element, event type, and relevant data.
Code Snippet
function LogClick() {
function handleClick(event) {
console.log(event.type); // "click"
console.log(event.target); // the DOM element clicked
console.log(event.currentTarget); // the element the handler is attached to
console.log(event.timeStamp); // when the event occurred
}
return <button onClick={handleClick}>Click Me</button>;
}Tip
24. Preventing Default Behavior đ
Many DOM elements have default browser behaviors â like form submission reloading the page, or links navigating away. Call event.preventDefault() to stop these defaults.
Code Snippet
function CustomLink() {
function handleClick(event) {
event.preventDefault();
console.log("Custom navigation logic here");
}
return <a href="/page" onClick={handleClick}>Custom Link</a>;
}Important
25. Event Propagation đ
When an event occurs on an element, it doesn't just trigger that element's handler â it travels through the DOM tree in two phases: capturing (top-down) and bubbling (bottom-up).
26. Event Bubbling âŦī¸
By default, most events bubble â after firing on the target element, they propagate upward through each ancestor element's handlers.
Code Snippet
function App() {
return (
<div onClick={() => console.log("Div clicked")}>
<button onClick={() => console.log("Button clicked")}>
Click Me
</button>
</div>
);
}
// Clicking the button logs: "Button clicked" then "Div clicked"Note
27. Event Capturing âŦī¸
To handle an event during the capturing phase (before it reaches the target), append Capture to the event handler prop name.
Code Snippet
function App() {
return (
<div onClickCapture={() => console.log("Div (capture phase)")}>
<button onClick={() => console.log("Button (bubble phase)")}>
Click Me
</button>
</div>
);
}
// Logs: "Div (capture phase)" then "Button (bubble phase)"28. Stopping Propagation â
Call event.stopPropagation() to prevent an event from continuing to bubble (or capture) further through the DOM tree.
Code Snippet
function Modal({ onClose }) {
function handleContentClick(event) {
event.stopPropagation(); // prevents closing when clicking inside
}
return (
<div className="overlay" onClick={onClose}>
<div className="modal-content" onClick={handleContentClick}>
Modal content
</div>
</div>
);
}Caution
29. Event Delegation đ¯
Event delegation attaches a single handler to a parent element instead of individual handlers on many children, relying on bubbling to catch events from descendants.
Code Snippet
function ItemList({ items, onItemClick }) {
function handleListClick(event) {
const itemId = event.target.dataset.id;
if (itemId) onItemClick(itemId);
}
return (
<ul onClick={handleListClick}>
{items.map((item) => (
<li key={item.id} data-id={item.id}>{item.name}</li>
))}
</ul>
);
}Information
30. Custom Event Handling Patterns đ¨
Custom hooks and callback props allow you to build reusable event-handling logic that can be shared across components.
Custom Hook: useClickOutside
function useClickOutside(ref, onClickOutside) {
useEffect(() => {
function handleClick(event) {
if (ref.current && !ref.current.contains(event.target)) {
onClickOutside();
}
}
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, [ref, onClickOutside]);
}Best Practice
31. Performance Considerations âĄ
- Debounce or throttle high-frequency events like scroll, resize, and mousemove.
- Avoid creating new inline functions on every render for performance-critical child components wrapped in memo.
- Use useCallback to memoize handlers passed to memoized child components.
- Prefer event delegation over attaching individual handlers to hundreds of list items.
Code Snippet
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);32. TypeScript with Events đˇ
TS provides specific event types for different element and event combinations, offering strong autocompletion and type safety.
Code Snippet
function Input() {
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
console.log(event.target.value);
}
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
console.log("Clicked");
}
return (
<>
<input onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</>
);
}Tip
33. Best Practices đ
- Name handler functions descriptively, prefixed with handle (e.g., handleSubmit).
- Always call preventDefault() in form submission handlers to avoid unwanted page reloads.
- Use arrow functions only when passing arguments; otherwise pass function references directly.
- Debounce or throttle handlers for high-frequency events like scrolling or resizing.
- Clean up manually attached document or window listeners inside useEffect cleanup functions.
34. Common Mistakes đĢ
- Calling the handler immediately: onClick={handleClick()} instead of onClick={handleClick}.
- Forgetting event.preventDefault() in form onSubmit handlers.
- Not cleaning up manually added event listeners, causing memory leaks.
- Overusing stopPropagation(), breaking unrelated event-driven features elsewhere in the app.
- Attaching heavy inline logic directly in JSX instead of extracting a named handler function.
Danger
35. Frequently Asked Questions â
Question
Answer
Question
Answer
Question
Answer
36. Summary đ
React's event system provides a consistent, cross-browser way to handle user interactions through SyntheticEvents, while still granting access to native browser behavior when needed. Understanding propagation, delegation, and proper cleanup patterns is essential for building responsive, bug-free interactive applications.