React Events vs DOM Events
React events are like a translator between you and the browser. They provide a consistent interface across all browsers while handling the complexity behind the scenes:
graph LR
A[User Action] --> B[Native DOM Event]
B --> C[React SyntheticEvent]
C --> D[Your Event Handler]
D --> E[State Update]
E --> F[Re-render]
F --> G[UI Updates]
style C fill:#f9f,stroke:#333,stroke-width:2px
style D fill:#9cf,stroke:#333,stroke-width:2px
Basic Event Handling
Handling Click Events
<pre><code> function ButtonExample() { const handleClick = () => { alert('Button clicked!'); }; return ( <button onClick={handleClick}> Click me </button> ); } // Using inline arrow functions function InlineExample() { return ( <button onClick={() => alert('Clicked!')}> Click me </button> ); } // Passing arguments to handlers function ArgumentExample() { const handleClick = (id) => { console.log(`Item ${id} clicked`); }; return ( <div> <button onClick={() => handleClick(1)}>Item 1</button> <button onClick={() => handleClick(2)}>Item 2</button> <button onClick={() => handleClick(3)}>Item 3</button> </div> ); } </code></pre>Event Object Properties
function EventProperties() {
const handleClick = (e) => {
console.log('Event type:', e.type);
console.log('Target:', e.target);
console.log('CurrentTarget:', e.currentTarget);
console.log('NativeEvent:', e.nativeEvent);
// Mouse event specific properties
console.log('ClientX/Y:', e.clientX, e.clientY);
console.log('PageX/Y:', e.pageX, e.pageY);
console.log('ScreenX/Y:', e.screenX, e.screenY);
// Keyboard event specific properties
console.log('Key:', e.key);
console.log('KeyCode:', e.keyCode);
console.log('AltKey:', e.altKey);
console.log('CtrlKey:', e.ctrlKey);
console.log('ShiftKey:', e.shiftKey);
console.log('MetaKey:', e.metaKey);
};
return (
);
}
Preventing Default Behavior
function PreventDefaultExample() {
const handleSubmit = (e) => {
e.preventDefault(); // Prevent form submission
console.log('Form submitted via JavaScript');
};
const handleLinkClick = (e) => {
e.preventDefault(); // Prevent navigation
console.log('Link clicked, but not navigating');
};
const handleContextMenu = (e) => {
e.preventDefault(); // Prevent context menu
console.log('Custom context menu');
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Type something" />
<button type="submit">Submit</button>
</form>
<a href="https://example.com" onClick={handleLinkClick}>
Click me (won't navigate)
</a>
<div
onContextMenu={handleContextMenu}
style={{ padding: '20px', backgroundColor: '#f0f0f0' }}
>
Right-click me for custom menu
</div>
</div>
);
}
Event Propagation
graph TD
A[Event Capturing Phase] --> B[Target Phase]
B --> C[Event Bubbling Phase]
D[Document] --> E[Parent Element]
E --> F[Target Element]
F --> G[Parent Element]
G --> H[Document]
A --> D
B --> F
C --> H
style B fill:#f96,stroke:#333,stroke-width:2px
style F fill:#f96,stroke:#333,stroke-width:2px
function EventPropagation() {
const handleOuterClick = (e) => {
console.log('Outer div clicked');
};
const handleInnerClick = (e) => {
console.log('Inner div clicked');
// e.stopPropagation(); // Uncomment to stop bubbling
};
const handleButtonClick = (e) => {
console.log('Button clicked');
e.stopPropagation(); // Stop event from bubbling up
};
return (
<div
onClick={handleOuterClick}
style={{ padding: '20px', backgroundColor: '#e0e0e0' }}
>
Outer Div
<div
onClick={handleInnerClick}
style={{ padding: '20px', backgroundColor: '#c0c0c0' }}
>
Inner Div
<button onClick={handleButtonClick}>
Click me (stops propagation)
</button>
</div>
</div>
);
}
// Capturing phase example
function CapturingExample() {
return (
<div
onClickCapture={() => console.log('Outer capture')}
onClick={() => console.log('Outer bubble')}
>
<button
onClickCapture={() => console.log('Button capture')}
onClick={() => console.log('Button bubble')}
>
Click me
</button>
</div>
);
}
Custom Events
// Custom event component
function CustomEventComponent({ onCustomEvent }) {
const triggerCustomEvent = () => {
const event = new CustomEvent('myCustomEvent', {
detail: { message: 'Hello from custom event!' }
});
window.dispatchEvent(event);
};
return (
);
}
// Listening to custom events
function CustomEventListener() {
const [message, setMessage] = useState('');
useEffect(() => {
const handleCustomEvent = (e) => {
setMessage(e.detail.message);
};
window.addEventListener('myCustomEvent', handleCustomEvent);
return () => {
window.removeEventListener('myCustomEvent', handleCustomEvent);
};
}, []);
return (
<div>
<CustomEventComponent />
<p>Custom event message: {message}</p>
</div>
);
}
Touch Events
function TouchEvents() {
const [touchPosition, setTouchPosition] = useState({ x: 0, y: 0 });
const [touchStatus, setTouchStatus] = useState('No touch');
const handleTouchStart = (e) => {
setTouchStatus('Touch started');
const touch = e.touches[0];
setTouchPosition({ x: touch.clientX, y: touch.clientY });
};
const handleTouchMove = (e) => {
setTouchStatus('Touch moving');
const touch = e.touches[0];
setTouchPosition({ x: touch.clientX, y: touch.clientY });
};
const handleTouchEnd = () => {
setTouchStatus('Touch ended');
};
return (
<div
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
height: '200px',
backgroundColor: '#f0f0f0',
touchAction: 'none' // Prevent scrolling
}}
>
<p>Status: {touchStatus}</p>
<p>Position: {touchPosition.x}, {touchPosition.y}</p>
</div>
);
}
Performance Considerations
Event Handler Binding
// Inefficient - creates new function on each render
function InefficientComponent() {
const [items, setItems] = useState([1, 2, 3]);
return (
<div>
{items.map(item => (
<button
key={item}
onClick={() => handleClick(item)} // New function each render
>
Item {item}
</button>
))}
</div>
);
}
// Better - use a stable function reference
function EfficientComponent() {
const [items, setItems] = useState([1, 2, 3]);
const handleClick = useCallback((item) => {
console.log('Clicked:', item);
}, []);
return (
<div>
{items.map(item => (
<ItemButton
key={item}
item={item}
onClick={handleClick}
/>
))}
</div>
);
}
const ItemButton = React.memo(({ item, onClick }) => {
return (
<button onClick={() => onClick(item)}>
Item {item}
</button>
);
});
Event Delegation
function EventDelegation() {
const [items, setItems] = useState(
Array.from({ length: 100 }, (_, i) => ({ id: i, name: `Item ${i}` }))
);
// Single event handler for all items
const handleListClick = (e) => {
const itemId = e.target.dataset.itemId;
if (itemId) {
console.log('Clicked item:', itemId);
}
};
return (
{items.map(item => (
-
{item.name}
))}
);
}
Common Event Patterns
Debounced Search
function DebouncedSearch() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
const debouncedSearch = useRef(
debounce(async (term) => {
if (!term) {
setResults([]);
return;
}
setIsSearching(true);
try {
const response = await fetch(`/api/search?q=${term}`);
const data = await response.json();
setResults(data);
} finally {
setIsSearching(false);
}
}, 300)
).current;
const handleChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
debouncedSearch(value);
};
return (
{isSearching && Searching...}
{results.map(result => (
- {result.name}
))}
);
}
// Debounce helper function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
Drag and Drop
function DragAndDrop() {
const [draggedItem, setDraggedItem] = useState(null);
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const handleDragStart = (e, item) => {
setDraggedItem(item);
e.dataTransfer.setData('text/plain', item);
};
const handleDragOver = (e) => {
e.preventDefault();
};
const handleDrop = (e, targetItem) => {
e.preventDefault();
const draggedIndex = items.indexOf(draggedItem);
const targetIndex = items.indexOf(targetItem);
const newItems = [...items];
newItems.splice(draggedIndex, 1);
newItems.splice(targetIndex, 0, draggedItem);
setItems(newItems);
setDraggedItem(null);
};
return (
<ul>
{items.map(item => (
<li
key={item}
draggable
onDragStart={(e) => handleDragStart(e, item)}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, item)}
style={{
padding: '10px',
margin: '5px',
backgroundColor: '#f0f0f0',
cursor: 'move'
}}
>
{item}
</li>
))}
</ul>
);
}
Best Practices
graph TD
A[Event Handling Best Practices] --> B[Use Semantic Events]
A --> C[Avoid Inline Arrow Functions]
A --> D[Prevent Default When Needed]
A --> E[Clean Up Event Listeners]
A --> F[Consider Performance]
B --> G[Use onChange for forms]
C --> H[Use useCallback for stability]
D --> I[e.preventDefault() for forms]
E --> J[Remove listeners in useEffect cleanup]
F --> K[Event delegation for lists]
style A fill:#9cf,stroke:#333,stroke-width:2px