Event Handling
Event Handling in React
Event handling in React is similar to handling events in DOM elements, but with some syntactic differences. React events are named using camelCase and you pass functions as event handlers.
Basic Event Handling
Simple Click Event
function Button() {
const handleClick = () => {
alert('Button clicked!');
};
return (
<button onClick={handleClick}>
Click me
</button>
);
}
Inline Event Handlers
function Button() {
return (
<button onClick={() => alert('Clicked!')}>
Click me
</button>
);
}
Event Object
React wraps native events in SyntheticEvent for cross-browser compatibility:
function Form() {
const handleSubmit = (e) => {
e.preventDefault(); // Prevent default form submission
console.log('Form submitted');
};
const handleInput = (e) => {
console.log('Input value:', e.target.value);
console.log('Input name:', e.target.name);
};
return (
<form onSubmit={handleSubmit}>
<input name="username" onChange={handleInput} />
<button type="submit">Submit</button>
</form>
);
}
Common Event Types
Mouse Events
function MouseEvents() {
return (
<div
onClick={() => console.log('Clicked')}
onDoubleClick={() => console.log('Double clicked')}
onMouseEnter={() => console.log('Mouse entered')}
onMouseLeave={() => console.log('Mouse left')}
onMouseMove={(e) => console.log(`Position: ${e.clientX}, ${e.clientY}`)}
onMouseDown={() => console.log('Mouse down')}
onMouseUp={() => console.log('Mouse up')}
style={{ padding: 50, backgroundColor: 'lightblue' }}
>
Interact with me!
</div>
);
}
Keyboard Events
function KeyboardEvents() {
const handleKeyPress = (e) => {
console.log('Key pressed:', e.key);
console.log('Key code:', e.keyCode);
console.log('Ctrl pressed:', e.ctrlKey);
console.log('Shift pressed:', e.shiftKey);
};
return (
<input
onKeyDown={(e) => console.log('Key down:', e.key)}
onKeyUp={(e) => console.log('Key up:', e.key)}
onKeyPress={handleKeyPress}
placeholder="Type something..."
/>
);
}
Form Events
function FormEvents() {
return (
<form>
<input
onChange={(e) => console.log('Changed:', e.target.value)}
onFocus={() => console.log('Focused')}
onBlur={() => console.log('Blurred')}
placeholder="Text input"
/>
<select onChange={(e) => console.log('Selected:', e.target.value)}>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<textarea
onSelect={(e) => console.log('Text selected')}
onCopy={() => console.log('Copied')}
onPaste={() => console.log('Pasted')}
onCut={() => console.log('Cut')}
/>
</form>
);
}
Passing Arguments to Event Handlers
Using Arrow Functions
function ItemList() {
const items = ['Apple', 'Banana', 'Orange'];
const handleClick = (item, index) => {
console.log(`Clicked ${item} at index ${index}`);
};
return (
<ul>
{items.map((item, index) => (
<li key={index}>
<button onClick={() => handleClick(item, index)}>
{item}
</button>
</li>
))}
</ul>
);
}
Using bind
function ItemList() {
const items = ['Apple', 'Banana', 'Orange'];
const handleClick = (item, index, event) => {
console.log(`Clicked ${item} at index ${index}`);
};
return (
<ul>
{items.map((item, index) => (
<li key={index}>
<button onClick={handleClick.bind(this, item, index)}>
{item}
</button>
</li>
))}
</ul>
);
}
Event Delegation
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build an app', done: false }
]);
// Single handler for all todo actions
const handleTodoAction = (e) => {
const action = e.target.dataset.action;
const todoId = parseInt(e.target.dataset.id);
switch (action) {
case 'toggle':
setTodos(todos.map(todo =>
todo.id === todoId ? { ...todo, done: !todo.done } : todo
));
break;
case 'delete':
setTodos(todos.filter(todo => todo.id !== todoId));
break;
}
};
return (
<ul onClick={handleTodoAction}>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
data-action="toggle"
data-id={todo.id}
readOnly
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button data-action="delete" data-id={todo.id}>
Delete
</button>
</li>
))}
</ul>
);
}
Preventing Default Behavior
function LinkComponent() {
const handleClick = (e) => {
e.preventDefault();
console.log('Link clicked but navigation prevented');
};
return (
<a href="https://example.com" onClick={handleClick}>
Click me (won't navigate)
</a>
);
}
function FormComponent() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted without page reload');
};
return (
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">Submit</button>
</form>
);
}
Stop Propagation
function EventPropagation() {
return (
<div onClick={() => console.log('Outer div clicked')}>
<button onClick={(e) => {
e.stopPropagation();
console.log('Button clicked (propagation stopped)');
}}>
Click me
</button>
</div>
);
}
Custom Events
function CustomEventComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const handleCustomEvent = (e) => {
setData(e.detail);
};
window.addEventListener('myCustomEvent', handleCustomEvent);
return () => {
window.removeEventListener('myCustomEvent', handleCustomEvent);
};
}, []);
const triggerCustomEvent = () => {
const event = new CustomEvent('myCustomEvent', {
detail: { message: 'Hello from custom event!' }
});
window.dispatchEvent(event);
};
return (
<div>
<button onClick={triggerCustomEvent}>Trigger Custom Event</button>
{data && <p>Received: {data.message}</p>}
</div>
);
}
Touch Events
function TouchComponent() {
const [touchPos, setTouchPos] = useState({ x: 0, y: 0 });
const handleTouchMove = (e) => {
const touch = e.touches[0];
setTouchPos({ x: touch.clientX, y: touch.clientY });
};
return (
<div
onTouchStart={() => console.log('Touch started')}
onTouchMove={handleTouchMove}
onTouchEnd={() => console.log('Touch ended')}
style={{
width: 200,
height: 200,
backgroundColor: 'lightgreen',
touchAction: 'none'
}}
>
Touch me!
<p>Position: {touchPos.x}, {touchPos.y}</p>
</div>
);
}
Drag and Drop Events
function DragDropComponent() {
const [draggedItem, setDraggedItem] = useState(null);
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const handleDragStart = (e, item) => {
setDraggedItem(item);
e.dataTransfer.effectAllowed = 'move';
};
const handleDragOver = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
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, index) => (
<li
key={index}
draggable
onDragStart={(e) => handleDragStart(e, item)}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, item)}
style={{
padding: 10,
margin: 5,
backgroundColor: 'lightgray',
cursor: 'move'
}}
>
{item}
</li>
))}
</ul>
);
}
Performance Considerations
Avoid Creating Functions in Render
// ❌ Creates new function on every render
function BadExample() {
return (
<button onClick={() => console.log('Clicked')}>
Click me
</button>
);
}
// ✅ Better - function created once
function GoodExample() {
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return (
<button onClick={handleClick}>
Click me
</button>
);
}
Debouncing Events
function SearchInput() {
const [value, setValue] = useState('');
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
const timer = setTimeout(() => {
setSearchTerm(value);
}, 500);
return () => clearTimeout(timer);
}, [value]);
return (
<div>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Search..."
/>
<p>Searching for: {searchTerm}</p>
</div>
);
}
Throttling Events
function useThrottle(callback, delay) {
const lastRun = useRef(Date.now());
return useCallback((...args) => {
if (Date.now() - lastRun.current >= delay) {
callback(...args);
lastRun.current = Date.now();
}
}, [callback, delay]);
}
function ScrollComponent() {
const [scrollY, setScrollY] = useState(0);
const handleScroll = useThrottle(() => {
setScrollY(window.scrollY);
}, 100);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
return <div>Scroll position: {scrollY}</div>;
}
Best Practices
- Use semantic event names - onClick not handleClick for props
- Prevent default when needed - Forms, links, etc.
- Clean up event listeners - Prevent memory leaks
- Use event delegation - For dynamic lists
- Debounce/throttle - For performance-sensitive events
- Avoid inline functions - When possible, for performance
- Handle errors gracefully - Try-catch in event handlers
Event handling is fundamental to creating interactive React applications. Master these patterns to build responsive user interfaces!