Event Handling
What is Event Handling?
Event handling is how you respond to user interactions like clicks, typing, and form submissions. React makes event handling straightforward with a consistent API across all browsers.
function Button() {
function handleClick() {
alert('Button clicked!');
}
return <button onClick={handleClick}>Click Me</button>;
}
Key differences from HTML:
- React events use camelCase:
onClicknotonclick - Pass function reference:
onClick={handleClick}notonClick="handleClick()" - Can’t return
falseto prevent default - must callpreventDefault()
onClick - Handling Clicks
The most common event handler for buttons and clickable elements:
Basic Click Handler
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
Inline Arrow Function
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
When to use which:
- Named function - for complex logic, reusable handlers
- Inline arrow function - for simple one-liners, passing arguments
Multiple Handlers
function ButtonGroup() {
const [message, setMessage] = useState('');
function handleSave() {
setMessage('Saved!');
}
function handleCancel() {
setMessage('Cancelled');
}
function handleDelete() {
setMessage('Deleted!');
}
return (
<div>
<p>{message}</p>
<button onClick={handleSave}>Save</button>
<button onClick={handleCancel}>Cancel</button>
<button onClick={handleDelete}>Delete</button>
</div>
);
}
Passing Arguments to Event Handlers
Using Arrow Functions
function ItemList() {
const items = ['Apple', 'Banana', 'Orange'];
function handleItemClick(item) {
alert(`You clicked: ${item}`);
}
return (
<ul>
{items.map(item => (
<li key={item}>
<button onClick={() => handleItemClick(item)}>
{item}
</button>
</li>
))}
</ul>
);
}
Using bind()
function ItemList() {
const items = ['Apple', 'Banana', 'Orange'];
function handleItemClick(item) {
alert(`You clicked: ${item}`);
}
return (
<ul>
{items.map(item => (
<li key={item}>
<button onClick={handleItemClick.bind(null, item)}>
{item}
</button>
</li>
))}
</ul>
);
}
Note: Arrow functions are more common and easier to read.
The Event Object
All event handlers receive a synthetic event object:
Accessing Event Properties
function ClickTracker() {
function handleClick(event) {
console.log('Button text:', event.target.textContent);
console.log('Click coordinates:', event.clientX, event.clientY);
console.log('Ctrl key held?', event.ctrlKey);
}
return <button onClick={handleClick}>Click Me</button>;
}
Preventing Default Behavior
function LinkButton() {
function handleClick(event) {
event.preventDefault(); // Prevent navigation
console.log('Link clicked, but not navigating');
}
return (
<a href="https://example.com" onClick={handleClick}>
Click (won't navigate)
</a>
);
}
Stopping Propagation
function NestedButtons() {
function handleOuterClick() {
console.log('Outer clicked');
}
function handleInnerClick(event) {
event.stopPropagation(); // Don't trigger outer click
console.log('Inner clicked');
}
return (
<div onClick={handleOuterClick} style={{ padding: '20px', background: '#eee' }}>
Outer
<button onClick={handleInnerClick}>Inner (stops propagation)</button>
</div>
);
}
onChange - Handling Input
Used for text inputs, textareas, and selects:
Text Input
function NameInput() {
const [name, setName] = useState('');
function handleChange(event) {
setName(event.target.value);
}
return (
<div>
<input
type="text"
value={name}
onChange={handleChange}
placeholder="Enter your name"
/>
<p>Hello, {name}!</p>
</div>
);
}
Inline onChange
function NameInput() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<p>Hello, {name}!</p>
</div>
);
}
Checkbox
function AgreementCheckbox() {
const [agreed, setAgreed] = useState(false);
return (
<div>
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
I agree to the terms
</label>
<button disabled={!agreed}>Submit</button>
</div>
);
}
Select Dropdown
function CountrySelector() {
const [country, setCountry] = useState('usa');
return (
<div>
<select value={country} onChange={(e) => setCountry(e.target.value)}>
<option value="usa">United States</option>
<option value="uk">United Kingdom</option>
<option value="canada">Canada</option>
</select>
<p>Selected: {country}</p>
</div>
);
}
onSubmit - Handling Form Submission
Basic Form
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
function handleSubmit(event) {
event.preventDefault(); // Prevent page reload
console.log('Submitting:', { email, password });
// Send to API here
}
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">Log In</button>
</form>
);
}
Form with Validation
function SignupForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
function handleSubmit(event) {
event.preventDefault();
// Validate
if (!email.includes('@')) {
setError('Invalid email address');
return;
}
setError('');
console.log('Valid email:', email);
// Submit to API
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
Other Common Events
onMouseEnter / onMouseLeave
function HoverButton() {
const [isHovered, setIsHovered] = useState(false);
return (
<button
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ backgroundColor: isHovered ? 'blue' : 'gray' }}
>
{isHovered ? 'Hovering!' : 'Hover me'}
</button>
);
}
onFocus / onBlur
function FocusInput() {
const [isFocused, setIsFocused] = useState(false);
return (
<input
type="text"
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder={isFocused ? 'Focused!' : 'Click to focus'}
style={{ borderColor: isFocused ? 'blue' : 'gray' }}
/>
);
}
onKeyDown / onKeyPress
function SearchBox() {
const [search, setSearch] = useState('');
function handleKeyDown(event) {
if (event.key === 'Enter') {
console.log('Search for:', search);
}
if (event.key === 'Escape') {
setSearch('');
}
}
return (
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Press Enter to search, Esc to clear"
/>
);
}
Common Patterns
1. Toggle Boolean State
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'ON' : 'OFF'}
</button>
);
}
2. Increment/Decrement Counter
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
3. Add Item to List
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
function handleAdd() {
if (input.trim()) {
setTodos([...todos, input]);
setInput('');
}
}
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
/>
<button onClick={handleAdd}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
</div>
);
}
4. Delete Item from List
function ItemList() {
const [items, setItems] = useState(['Apple', 'Banana', 'Orange']);
function handleDelete(index) {
setItems(items.filter((_, i) => i !== index));
}
return (
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => handleDelete(index)}>Delete</button>
</li>
))}
</ul>
);
}
5. Multiple Input Form
function UserForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
});
function handleChange(event) {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value
});
}
function handleSubmit(event) {
event.preventDefault();
console.log('Form data:', formData);
}
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
name="age"
value={formData.age}
onChange={handleChange}
placeholder="Age"
/>
<button type="submit">Submit</button>
</form>
);
}
Best Practices
✅ DO:
// Use named functions for complex handlers
function handleComplexOperation() {
// ... lots of logic
}
<button onClick={handleComplexOperation}>Do Thing</button>
// Prevent default for forms
function handleSubmit(event) {
event.preventDefault();
// ...
}
// Use controlled components (value + onChange)
<input value={name} onChange={(e) => setName(e.target.value)} />
// Pass functions, not function calls
<button onClick={handleClick}>Click</button> // ✅
// Use arrow functions to pass arguments
<button onClick={() => handleClick(id)}>Click</button>
❌ DON’T:
// Don't call the function immediately
<button onClick={handleClick()}>Click</button> // ❌ Calls on render!
// Don't forget preventDefault in forms
function handleSubmit(event) {
// Missing event.preventDefault() - page will reload!
console.log('submitting');
}
// Don't use uncontrolled components without a reason
<input onChange={(e) => setName(e.target.value)} /> // ❌ Missing value prop
// Don't create functions inside JSX in loops (performance)
{items.map(item => (
<button onClick={() => { /* complex logic */ }}> // ❌ Bad in loops
{item}
</button>
))}
Event Handler Performance
Avoid Creating Functions in Render (for large lists)
// ❌ Bad - creates new function on every render
function ItemList({ items }) {
return items.map(item => (
<button onClick={() => console.log(item.id)}>
{item.name}
</button>
));
}
// ✅ Better - use useCallback for expensive operations
function ItemList({ items }) {
const handleClick = useCallback((id) => {
console.log(id);
}, []);
return items.map(item => (
<button onClick={() => handleClick(item.id)}>
{item.name}
</button>
));
}
Summary
Common Events:
onClick- Button/element clicksonChange- Input value changesonSubmit- Form submissiononMouseEnter/onMouseLeave- HoveronFocus/onBlur- Focus stateonKeyDown- Keyboard input
Key Concepts:
- Use camelCase:
onClick, notonclick - Pass function reference:
onClick={handleClick} - Event object:
event.target.value,event.preventDefault() - Controlled components:
value={state}+onChange={handler}
Form Handling:
function Form() {
const [value, setValue] = useState('');
function handleSubmit(event) {
event.preventDefault(); // Prevent page reload
console.log(value);
}
return (
<form onSubmit={handleSubmit}>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
Next Article: Conditional Rendering