React Events vs Vanilla JavaScript
In vanilla JavaScript, you attach event listeners directly to DOM elements. React takes a different approach with SyntheticEvents that provide consistency across browsers and integrate seamlessly with React's component system.
// HTML
<button id="myButton">Click me</button>
<input id="myInput" type="text">
// JavaScript
const button = document.getElementById('myButton');
const input = document.getElementById('myInput');
// Adding event listeners
button.addEventListener('click', function(event) {
console.log('Button clicked!');
console.log(event.target);
});
input.addEventListener('change', function(event) {
console.log('Input changed:', event.target.value);
});
// Form submission
const form = document.getElementById('myForm');
form.addEventListener('submit', function(event) {
event.preventDefault();
console.log('Form submitted');
});
function MyComponent() {
// Event handlers as functions
const handleClick = (event) => {
console.log('Button clicked!');
console.log(event.target);
};
const handleChange = (event) => {
console.log('Input changed:', event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Form submitted');
};
return (
<div>
<button onClick={handleClick}>Click me</button>
<input type="text" onChange={handleChange} />
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
Understanding SyntheticEvent
React wraps native events in SyntheticEvent objects. These provide the same interface as native events but work consistently across all browsers and integrate with React's event system.
🎯 SyntheticEvent Benefits
- Cross-browser compatibility - Same API across all browsers
- Event pooling - Better performance through object reuse
- preventDefault() and stopPropagation() - Standard methods work everywhere
- Consistent behavior - No browser-specific quirks
function EventExample() {
const handleEvent = (event) => {
console.log('Event type:', event.type); // 'click', 'change', etc.
console.log('Target element:', event.target); // Element that triggered event
console.log('Current target:', event.currentTarget); // Element with event handler
console.log('Native event:', event.nativeEvent); // Access to original DOM event
// Prevent default behavior
event.preventDefault();
// Stop event propagation
event.stopPropagation();
};
return (
<div onClick={handleEvent}>
<button onClick={handleEvent}>
Click me to see event details
</button>
</div>
);
}
Common Event Types
React supports all standard DOM events. Here are the most commonly used ones:
🖱️ Mouse Events
<button
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseOver={handleMouseOver}
onMouseMove={handleMouseMove}
>
Interactive Button
</button>
⌨️ Keyboard Events
<input
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onKeyPress={handleKeyPress}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder="Type something..."
/>
// Example key handler
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
console.log('Enter pressed!');
}
};
📝 Form Events
<form onSubmit={handleSubmit}>
<input
onChange={handleChange}
onInput={handleInput}
onFocus={handleFocus}
onBlur={handleBlur}
/>
<select onChange={handleSelectChange}>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<button type="submit">Submit</button>
</form>
🎯 Focus Events
<div>
<input
onFocus={() => console.log('Input focused')}
onBlur={() => console.log('Input blurred')}
placeholder="Focus me"
/>
<div
tabIndex={0}
onFocus={() => console.log('Div focused')}
onBlur={() => console.log('Div blurred')}
>
Focusable div
</div>
</div>
Event Handler Patterns
There are several ways to write event handlers in React. Let's explore the most common patterns:
1. Inline Arrow Functions
Quick for simple handlers, but creates new function on each render.
function Component() {
const [count, setCount] = useState(0);
return (
<div>
{/* Simple inline handler */}
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
{/* Inline with parameters */}
<button onClick={() => setCount(count + 5)}>
Add 5
</button>
{/* Inline with event object */}
<input onChange={(e) => console.log(e.target.value)} />
</div>
);
}
2. Defined Function Handlers
Better performance for complex logic, function reference stays the same.
function Component() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// Defined handlers
const handleNameChange = (event) => {
setName(event.target.value);
};
const handleEmailChange = (event) => {
setEmail(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Submitting:', { name, email });
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={handleNameChange}
placeholder="Name"
/>
<input
value={email}
onChange={handleEmailChange}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}
3. Generic Handlers with Parameters
Reusable handlers that work with multiple elements.
function FormComponent() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
});
// Generic handler for all form fields
const handleInputChange = (fieldName) => (event) => {
setFormData(prev => ({
...prev,
[fieldName]: event.target.value
}));
};
// Alternative: single handler using name attribute
const handleChange = (event) => {
const { name, value } = event.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
return (
<div>
{/* Using generic handler with parameter */}
<input
value={formData.name}
onChange={handleInputChange('name')}
placeholder="Name"
/>
{/* Using single handler with name attribute */}
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
name="age"
value={formData.age}
onChange={handleChange}
placeholder="Age"
/>
</div>
);
}
Practical Event Handling Examples
🎮 Interactive Counter with Multiple Events
function InteractiveCounter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const increment = () => setCount(prev => prev + step);
const decrement = () => setCount(prev => prev - step);
const reset = () => setCount(0);
const handleKeyPress = (event) => {
switch(event.key) {
case 'ArrowUp':
increment();
break;
case 'ArrowDown':
decrement();
break;
case 'r':
reset();
break;
default:
break;
}
};
return (
<div
tabIndex={0}
onKeyDown={handleKeyPress}
style={{ padding: '20px', outline: 'none' }}
>
<h2>Count: {count}</h2>
<div>
<button onClick={decrement}>-{step}</button>
<button onClick={increment}>+{step}</button>
<button onClick={reset}>Reset</button>
</div>
<div>
<label>
Step size:
<input
type="number"
value={step}
onChange={(e) => setStep(Number(e.target.value))}
min="1"
/>
</label>
</div>
<p>Try using arrow keys and 'r' to reset!</p>
</div>
);
}
📝 Dynamic Form with Validation
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const validateField = (name, value) => {
switch(name) {
case 'name':
return value.length < 2 ? 'Name must be at least 2 characters' : '';
case 'email':
return !/\S+@\S+\.\S+/.test(value) ? 'Email is invalid' : '';
case 'message':
return value.length < 10 ? 'Message must be at least 10 characters' : '';
default:
return '';
}
};
const handleChange = (event) => {
const { name, value } = event.target;
// Update form data
setFormData(prev => ({
...prev,
[name]: value
}));
// Validate field and update errors
const error = validateField(name, value);
setErrors(prev => ({
...prev,
[name]: error
}));
};
const handleSubmit = async (event) => {
event.preventDefault();
setIsSubmitting(true);
// Validate all fields
const newErrors = {};
Object.keys(formData).forEach(key => {
const error = validateField(key, formData[key]);
if (error) newErrors[key] = error;
});
setErrors(newErrors);
// Submit if no errors
if (Object.keys(newErrors).length === 0) {
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Form submitted:', formData);
// Reset form
setFormData({ name: '', email: '', message: '' });
alert('Message sent successfully!');
} catch (error) {
console.error('Submission failed:', error);
}
}
setIsSubmitting(false);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name:</label>
<input
id="name"
name="name"
type="text"
value={formData.name}
onChange={handleChange}
onBlur={handleChange} // Validate on blur too
/>
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<label htmlFor="email">Email:</label>
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
onBlur={handleChange}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<label htmlFor="message">Message:</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
onBlur={handleChange}
rows={4}
/>
{errors.message && <span className="error">{errors.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
);
}
Event Handling Best Practices
✅ Performance Tips
- Use defined functions for complex handlers
- Avoid creating new functions in render
- Use useCallback for expensive operations
- Debounce frequently fired events
✅ Accessibility
- Support keyboard navigation
- Use semantic HTML elements
- Provide ARIA labels when needed
- Handle focus management properly
✅ Error Handling
- Always validate user input
- Prevent form submission on errors
- Show clear error messages
- Handle async operation failures
❌ Common Mistakes
- Forgetting to call preventDefault()
- Not handling async operations properly
- Creating new functions on every render
- Forgetting to validate form inputs
What's Next?
You now know how to handle user interactions in React using the powerful event system. Next, you'll learn how to conditionally render content based on state and props.
🚀 Continue Learning
- Conditional Rendering & Lists - Show different content dynamically
- React Hooks - Advanced state management with useEffect
- State Management - Managing complex application state
- Transition Guide - Complete migration strategies