React Events: (Synthetic, Hooks)
React Events
SyntheticEvent wrapper to create a single, cross-browser API, so you don’t have to worry about differences between Firefox, Chrome, or Safari.Watch Tutorial
- Introduction to React Events
- Synthetic Events (SyntheticEvent)
- Handling Events in JSX
- Common Event Types
- Event Binding &
thisin Class Components - Passing Arguments to Handlers
- Preventing Default Behavior
- Event Propagation Bubbling & Capturing
- Events & State Updates
- Events with React Hooks
- Quick Cheatsheet
- FAQ
1 Introduction to React Events
The core idea is that React doesn’t use native DOM events directly. Instead, it gives you a SyntheticEvent object that looks and behaves the same in every browser. Here are the three most important practical differences from standard HTML/JavaScript:
-
CamelCase Names: You write
onClickinstead ofonclick. -
Function Reference: You pass a function reference (
{handleClick}), not a string. This is a common source of bugs; writing{handleClick()}would call the function immediately when the component renders. -
Prevent Default: You must explicitly call
e.preventDefault(). The oldreturn falsetrick does not work.
Key differences from standard HTML events:
| Feature | HTML DOM Events | React Events |
|---|---|---|
| Naming | onclick (lowercase) |
onClick (camelCase) |
| Handler value | String: "doSomething()" |
Function reference: {doSomething} |
| Event object | Native browser event | SyntheticEvent wrapper |
| Event delegation | Per-element listeners | Single root listener (React 17+) |
| Prevent default | return false works |
Must call e.preventDefault() |
2 Synthetic Events
React wraps native browser events in a SyntheticEvent object. This object has the same interface as a native event (target, currentTarget, preventDefault(), stopPropagation(), etc.) but normalizes behavior across all browsers.
From React 17 onwards, events are attached to the root DOM container instead of the
document. This means multiple React apps on the same page no longer conflict. Also, SyntheticEvent pooling was removed, event objects are no longer reused, so you can safely read properties asynchronously.
function SyntheticEventDemo() { const handleClick = (e) => { // e is a SyntheticEvent — same interface as native events console.log('Type:', e.type); // "click" console.log('Target:', e.target); // the button element console.log('currentTarget:', e.currentTarget); // same as target here console.log('nativeEvent:', e.nativeEvent); // access the raw browser event }; return ( <button onClick={handleClick}> Click Me </button> ); }
3 Handling Events in JSX
Event handlers in JSX are written using camelCase attributes. You pass a function reference, not a function call.
Common Mistake: Writing
onClick={handleClick()}calls the function immediately during render. Always writeonClick={handleClick}(without parentheses) to pass the reference.
import { useState } from 'react'; function EventHandling() { const [msg, setMsg] = useState(''); // 1. Named handler (preferred — keeps JSX clean) const handleClick = () => setMsg('Button clicked!'); return ( <div> {/* Named handler reference */} <button onClick={handleClick}>Named Handler</button> {/* Inline arrow function (fine for simple cases) */} <button onClick={() => setMsg('Inline handler!')}>Inline</button> {/* WRONG — calls immediately during render */} {/* <button onClick={handleClick()}>Wrong!</button> */} <p>{msg}</p> </div> ); }
4 Common Event Types
React supports all native browser events. Below are the most frequently used ones organized by categories.
| Category | Event Name | Description |
|---|---|---|
| Mouse | onClick |
Fires on mouse click or tap |
onDoubleClick |
Fires on double-click | |
onMouseEnter |
When cursor enters element | |
onMouseLeave |
When cursor leaves element | |
| Keyboard | onKeyDown |
Key pressed down |
onKeyUp |
Key released | |
onKeyPress |
Key press (deprecated) | |
| Form | onChange |
Input value changed |
onSubmit |
Form submitted | |
onFocus |
Element gains focus | |
onBlur |
Element loses focus | |
| Clipboard | onCopy / onPaste |
Copy or paste action |
| Touch | onTouchStart |
Touch begins |
onTouchEnd |
Touch ends | |
| Drag | onDrag / onDrop |
Drag and drop events |
| Scroll | onScroll |
Element is scrolled |
Code Example
Multiple Event Types
function MultipleEvents() { const [log, setLog] = useState([]); const addLog = (msg) => setLog((prev) => [msg, ...prev.slice(0, 4)]); return ( <div> <input onFocus={() => addLog('Input focused')} onBlur={() => addLog('Input blurred')} onChange={(e) => addLog(`Value: ${e.target.value}`)} onKeyDown={(e) => addLog(`Key: ${e.key}`)} placeholder="Type something..." /> <button onMouseEnter={() => addLog('Mouse entered button')} onMouseLeave={() => addLog('Mouse left button')} onClick={() => addLog('Button clicked!')} onDoubleClick={() => addLog('Double clicked!')} > Interact with me </button> <ul>{log.map((l, i) => <li key={i}>{l}</li>)}</ul> </div> ); }
5 Event Binding & this in Class Components
In class components, event handlers lose the this context if not properly bound. There are three common approaches:
class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; // Method 1: Bind in constructor (recommended for classes) this.handleIncrement = this.handleIncrement.bind(this); } handleIncrement() { this.setState({ count: this.state.count + 1 }); } // Method 2: Class field (arrow function — auto-binds this) handleDecrement = () => { this.setState({ count: this.state.count - 1 }); }; render() { return ( <div> <p>Count: {this.state.count}</p> {/* Method 1: constructor-bound */} <button onClick={this.handleIncrement}>+</button> {/* Method 2: class field arrow */} <button onClick={this.handleDecrement}>−</button> {/* Method 3: inline bind (creates new fn every render) */} <button onClick={this.handleIncrement.bind(this)}>Also +</button> </div> ); } }
Best Practice
In modern React, prefer functional components with hooks, the this binding problem disappears entirely because arrow functions and hooks handle context naturally.
6 Passing Arguments to Event Handlers
When you need to pass extra data to an event handler (e.g., an item ID), wrap the handler in an inline arrow function:
function ItemList() { const items = [ { id: 1, name: 'Web Design' }, { id: 2, name: 'SEO' }, { id: 3, name: 'Performance Marketing' }, ]; // Handler receives the custom argument AND the event object const handleSelect = (id, name, event) => { event.preventDefault(); console.log(`Selected: ${name} (ID: ${id})`); }; return ( <ul> {items.map((item) => ( <li key={item.id}> <button {/* Wrap in arrow fn to pass extra args */} onClick={(e) => handleSelect(item.id, item.name, e)} > Select {item.name} </button> </li> ))} </ul> ); }
7 Preventing Default Behavior
Use e.preventDefault() to stop the browser’s default action. Unlike plain HTML, returning false does NOT work in React event handlers.
function LoginForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = (e) => { e.preventDefault(); // Stops page reload — REQUIRED in React // return false; ← This does NOT work in React! console.log('Logging in:', email, password); // Call your API here... }; const handleLinkClick = (e) => { e.preventDefault(); // Prevents navigation console.log('Link clicked — handled in JS'); }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" /> <button type="submit">Login</button> <a href="/forgot" onClick={handleLinkClick}>Forgot password?</a> </form> ); }
8 Event Propagation (Bubbling & Capturing)
By default, React events bubble they fire on the target element first, then propagate up through parent elements. Use e.stopPropagation() to prevent bubbling. For capture-phase handling, append Capture to the event name (e.g., onClickCapture).
function PropagationDemo() { const handleParent = () => console.log('Parent clicked (bubbled)'); const handleChild = (e) => { e.stopPropagation(); // Prevents event reaching parent console.log('Child clicked — stopped here'); }; // Capture phase: fires top → down (before bubbling) const handleCapture = () => console.log('Captured at parent (top-down)'); return ( <div onClick={handleParent} onClickCapture={handleCapture} {/* fires first */} style={{ padding: '20px', background: '#EBF4FF' }} > Parent (click me too) <button onClick={handleChild}> Child (stops propagation) </button> </div> ); } /* Propagation order when you click the button: 1. onClickCapture (parent) — capture phase, top-down 2. onClick (child) — target phase 3. stopPropagation() — bubbling STOPPED 4. handleParent — NOT called */
9 Events & State Updates
Events are the primary way users trigger state changes in React. The pattern is: event → handler → setState → re-render.
import { useState } from 'react'; function CourseEnrollment() { const [enrolled, setEnrolled] = useState(false); const [count, setCount] = useState(0); const [search, setSearch] = useState(''); // Toggle boolean state const toggleEnroll = () => setEnrolled((prev) => !prev); // Functional update for reliable counter (avoids stale closure) const increment = () => setCount((prev) => prev + 1); // Controlled input — onChange syncs every keystroke const handleSearch = (e) => setSearch(e.target.value); return ( <div> <button onClick={toggleEnroll}> {enrolled ? 'Unenroll' : 'Enroll Now'} </button> <button onClick={increment}> Lesson: {count} </button> <input type="text" value={search} onChange={handleSearch} placeholder="Search courses..." /> <p>Searching: {search}</p> </div> ); }
10 Events with React Hooks
Hooks unlock powerful patterns for event handling useCallback memoizes handlers, useRef accesses DOM nodes, and useEffect adds global event listeners.
useCallback: Memoized Event Handlers
import { useState, useCallback } from 'react'; function SearchBar({ onSearch }) { const [query, setQuery] = useState(''); // useCallback prevents re-creating this fn on every render // Useful when passing handlers to memoized child components const handleChange = useCallback((e) => { const val = e.target.value; setQuery(val); onSearch(val); }, [onSearch]); // Re-create only if onSearch changes return ( <input value={query} onChange={handleChange} placeholder="Search..." /> ); }
useRef : DOM Event Access
import { useRef } from 'react'; function AutoFocusInput() { const inputRef = useRef(null); const handleFocusClick = () => { inputRef.current.focus(); // Directly access DOM node inputRef.current.select(); // Select all text }; return ( <div> <input ref={inputRef} defaultValue="7Scribes" /> <button onClick={handleFocusClick}> Focus & Select Input </button> </div> ); }
useEffect: Global Event Listeners
import { useState, useEffect } from 'react'; function KeyboardShortcuts() { const [lastKey, setLastKey] = useState(''); useEffect(() => { const handleKeyDown = (e) => { if (e.ctrlKey && e.key === 's') { e.preventDefault(); console.log('Save shortcut!'); } setLastKey(e.key); }; // Attach global listener window.addEventListener('keydown', handleKeyDown); // IMPORTANT: Clean up to avoid memory leaks return () => window.removeEventListener('keydown', handleKeyDown); }, []); // Empty array = runs once on mount return <p>Last key pressed: {lastKey || 'None'}</p>; }
Custom Event Hook
import { useState, useEffect } from 'react'; // Reusable custom hook for detecting key presses function useKeyPress(targetKey) { const [pressed, setPressed] = useState(false); useEffect(() => { const onDown = ({ key }) => key === targetKey && setPressed(true); const onUp = ({ key }) => key === targetKey && setPressed(false); window.addEventListener('keydown', onDown); window.addEventListener('keyup', onUp); return () => { window.removeEventListener('keydown', onDown); window.removeEventListener('keyup', onUp); }; }, [targetKey]); return pressed; } // Usage function App() { const enterPressed = useKeyPress('Enter'); return <p>Enter key: {enterPressed ? 'Pressed' : 'Released'}</p>; }
11 Quick Cheatsheet
Common patterns for event handling in React (functional components).
| Pattern | Code Snippet / Example |
|---|---|
|
Basic Handler
onClick reference |
Pass function reference, not invocation.
|
|
Pass Arguments
Inline arrow wrapper |
Use arrow function to pass extra data + event object.
|
|
Prevent Default
e.preventDefault() |
return false does NOT work in React. |
|
Stop Propagation
e.stopPropagation() |
|
|
Controlled Input
two-way binding |
|
|
Global Listener
keyboard / window events |
|
|
Capture Phase
onClickCapture |
|
|
Memoized Handler
useCallback optimization |
Prevents recreation on each render, ideal for memoized children.
|
Pro tip: Always define handlers outside JSX when possible, and useuseCallbackonly when passing to optimized child components. For most cases, simple arrow functions inside components are fine.
12 FAQ
The most common questions about React Events, answered with clarity and code.
1 What is a SyntheticEvent in React?
2 Why does React use camelCase for event names?
3 Why can’t I write onClick={handleClick()} with parentheses?
4 Does return false prevent default behavior in React?
5 What is event bubbling and how do I stop it?
6 What is event capturing and when do I use it?
7 How do I pass extra data to an event handler?
8 Why does my class component event handler lose ‘this’?
9 What is the difference between onChange and onInput in React?
10 How do I add a global event listener (like keyboard shortcuts) in React?
useEffect(() => { const handler = (e) => { /* ... */ }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, []);
11 What is useCallback and when should I use it for event handlers?
12 Can I read event properties asynchronously (e.g., inside setTimeout)?
13 How do I handle form submission in React?
const handleSubmit = (e) => { e.preventDefault(); // required! // access state values directly submitToAPI({ email, password }); }; // JSX: <form onSubmit={handleSubmit}>...</form>
