Table of Contents
Event Handling in React
Event handling is crucial for creating interactive React applications. React uses SyntheticEvents, which are wrappers around native DOM events that provide consistent behavior across different browsers.
Basic Event Handling
React events are named using camelCase and pass a SyntheticEvent object:
function EventExamples() {
const handleClick = (event) => {
console.log("Button clicked!", event);
console.log("Event type:", event.type);
console.log("Target element:", event.target);
};
const handleMouseOver = (event) => {
event.target.style.backgroundColor = "lightblue";
};
const handleMouseOut = (event) => {
event.target.style.backgroundColor = "";
};
const handleKeyPress = (event) => {
if (event.key === "Enter") {
console.log("Enter key pressed!");
}
};
return (
<div>
<button onClick={handleClick}>
Click Me
</button>
<div
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
style={{ padding: "20px", border: "1px solid #ccc", margin: "10px" }}
>
Hover over me!
</div>
<input
type="text"
onKeyPress={handleKeyPress}
placeholder="Press Enter"
/>
</div>
);
}
Event Object and preventDefault
The event object provides useful methods and properties:
function LinkExample() {
const handleLinkClick = (event) => {
event.preventDefault(); // Prevent default link behavior
console.log("Link clicked, but navigation prevented");
// Custom logic here
alert("Custom link handler executed!");
};
const handleFormSubmit = (event) => {
event.preventDefault(); // Prevent form submission
console.log("Form submission prevented");
// Handle form data manually
const formData = new FormData(event.target);
console.log("Form data:", Object.fromEntries(formData));
};
return (
<div>
<a href="https://example.com" onClick={handleLinkClick}>
Click this link (won't navigate)
</a>
<form onSubmit={handleFormSubmit}>
<input name="username" placeholder="Username" required />
<button type="submit">Submit</button>
</form>
</div>
);
}
Passing Parameters to Event Handlers
There are several ways to pass additional parameters:
function ParameterExamples() {
const [selectedItem, setSelectedItem] = useState(null);
const items = [
{ id: 1, name: "Apple", category: "Fruit" },
{ id: 2, name: "Carrot", category: "Vegetable" },
{ id: 3, name: "Banana", category: "Fruit" }
];
// Method 1: Arrow function in JSX
const handleItemClick1 = (item) => {
setSelectedItem(item);
console.log("Selected:", item.name);
};
// Method 2: Bind method
const handleItemClick2 = (item, event) => {
setSelectedItem(item);
console.log("Selected:", item.name, "Event:", event);
};
// Method 3: Data attributes
const handleItemClick3 = (event) => {
const itemId = parseInt(event.target.dataset.itemId);
const item = items.find(i => i.id === itemId);
setSelectedItem(item);
};
return (
<div>
<h3>Method 1: Arrow Function</h3>
{items.map(item => (
<button
key={item.id}
onClick={() => handleItemClick1(item)}
>
{item.name}
</button>
))}
<h3>Method 2: Bind</h3>
{items.map(item => (
<button
key={item.id}
onClick={handleItemClick2.bind(null, item)}
>
{item.name}
</button>
))}
<h3>Method 3: Data Attributes</h3>
{items.map(item => (
<button
key={item.id}
data-item-id={item.id}
onClick={handleItemClick3}
>
{item.name}
</button>
))}
{selectedItem && (
<div>
<h4>Selected: {selectedItem.name}</h4>
<p>Category: {selectedItem.category}</p>
</div>
)}
</div>
);
}
Form Handling in React
React provides two approaches for handling forms: controlled and uncontrolled components.
Controlled Components
Form elements whose values are controlled by React state:
function ControlledForm() {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
age: "",
gender: "",
country: "",
interests: [],
newsletter: false,
comments: ""
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleInputChange = (event) => {
const { name, value, type, checked } = event.target;
setFormData(prevData => ({
...prevData,
[name]: type === "checkbox" ? checked : value
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors(prevErrors => ({
...prevErrors,
[name]: ""
}));
}
};
const handleInterestChange = (event) => {
const { value, checked } = event.target;
setFormData(prevData => ({
...prevData,
interests: checked
? [...prevData.interests, value]
: prevData.interests.filter(interest => interest !== value)
}));
};
const validateForm = () => {
const newErrors = {};
if (!formData.firstName.trim()) {
newErrors.firstName = "First name is required";
}
if (!formData.lastName.trim()) {
newErrors.lastName = "Last name is required";
}
if (!formData.email.trim()) {
newErrors.email = "Email is required";
} else if (!/S+@S+.S+/.test(formData.email)) {
newErrors.email = "Email is invalid";
}
if (!formData.age) {
newErrors.age = "Age is required";
} else if (formData.age < 1 || formData.age > 120) {
newErrors.age = "Age must be between 1 and 120";
}
if (!formData.gender) {
newErrors.gender = "Please select a gender";
}
if (!formData.country) {
newErrors.country = "Please select a country";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!validateForm()) {
return;
}
setIsSubmitting(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
console.log("Form submitted:", formData);
alert("Form submitted successfully!");
// Reset form
setFormData({
firstName: "",
lastName: "",
email: "",
age: "",
gender: "",
country: "",
interests: [],
newsletter: false,
comments: ""
});
} catch (error) {
console.error("Submission error:", error);
alert("Submission failed. Please try again.");
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit} className="controlled-form">
<h2>User Registration Form</h2>
<div className="form-row">
<div className="form-group">
<label htmlFor="firstName">First Name:</label>
<input
type="text"
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleInputChange}
className={errors.firstName ? "error" : ""}
/>
{errors.firstName && <span className="error-message">{errors.firstName}</span>}
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name:</label>
<input
type="text"
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleInputChange}
className={errors.lastName ? "error" : ""}
/>
{errors.lastName && <span className="error-message">{errors.lastName}</span>}
</div>
</div>
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleInputChange}
className={errors.email ? "error" : ""}
/>
{errors.email && <span className="error-message">{errors.email}</span>}
</div>
<div className="form-group">
<label htmlFor="age">Age:</label>
<input
type="number"
id="age"
name="age"
value={formData.age}
onChange={handleInputChange}
className={errors.age ? "error" : ""}
/>
{errors.age && <span className="error-message">{errors.age}</span>}
</div>
<div className="form-group">
<label>Gender:</label>
<div className="radio-group">
<label>
<input
type="radio"
name="gender"
value="male"
checked={formData.gender === "male"}
onChange={handleInputChange}
/>
Male
</label>
<label>
<input
type="radio"
name="gender"
value="female"
checked={formData.gender === "female"}
onChange={handleInputChange}
/>
Female
</label>
<label>
<input
type="radio"
name="gender"
value="other"
checked={formData.gender === "other"}
onChange={handleInputChange}
/>
Other
</label>
</div>
{errors.gender && <span className="error-message">{errors.gender}</span>}
</div>
<div className="form-group">
<label htmlFor="country">Country:</label>
<select
id="country"
name="country"
value={formData.country}
onChange={handleInputChange}
className={errors.country ? "error" : ""}
>
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
<option value="au">Australia</option>
<option value="de">Germany</option>
</select>
{errors.country && <span className="error-message">{errors.country}</span>}
</div>
<div className="form-group">
<label>Interests:</label>
<div className="checkbox-group">
{["Programming", "Design", "Music", "Sports", "Reading"].map(interest => (
<label key={interest}>
<input
type="checkbox"
value={interest}
checked={formData.interests.includes(interest)}
onChange={handleInterestChange}
/>
{interest}
</label>
))}
</div>
</div>
<div className="form-group">
<label>
<input
type="checkbox"
name="newsletter"
checked={formData.newsletter}
onChange={handleInputChange}
/>
Subscribe to newsletter
</label>
</div>
<div className="form-group">
<label htmlFor="comments">Comments:</label>
<textarea
id="comments"
name="comments"
value={formData.comments}
onChange={handleInputChange}
rows="4"
placeholder="Any additional comments..."
></textarea>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</button>
<div className="form-data-preview">
<h3>Form Data Preview:</h3>
<pre>{JSON.stringify(formData, null, 2)}</pre>
</div>
</form>
);
}
Uncontrolled Components
Form elements that maintain their own state using refs:
import React, { useRef } from 'react';
function UncontrolledForm() {
const formRef = useRef();
const nameRef = useRef();
const emailRef = useRef();
const messageRef = useRef();
const handleSubmit = (event) => {
event.preventDefault();
const formData = {
name: nameRef.current.value,
email: emailRef.current.value,
message: messageRef.current.value
};
console.log("Uncontrolled form data:", formData);
// Reset form
formRef.current.reset();
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<h2>Contact Form (Uncontrolled)</h2>
<div>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
ref={nameRef}
defaultValue="John Doe"
required
/>
</div>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
ref={emailRef}
required
/>
</div>
<div>
<label htmlFor="message">Message:</label>
<textarea
id="message"
ref={messageRef}
rows="4"
required
></textarea>
</div>
<button type="submit">Send Message</button>
</form>
);
}
Advanced Event Handling Patterns
Some useful patterns for complex event handling:
function AdvancedEventHandling() {
const [draggedItem, setDraggedItem] = useState(null);
const [dropZoneActive, setDropZoneActive] = useState(false);
// Drag and Drop
const handleDragStart = (event, item) => {
setDraggedItem(item);
event.dataTransfer.effectAllowed = "move";
};
const handleDragOver = (event) => {
event.preventDefault();
setDropZoneActive(true);
};
const handleDragLeave = () => {
setDropZoneActive(false);
};
const handleDrop = (event) => {
event.preventDefault();
setDropZoneActive(false);
if (draggedItem) {
console.log("Dropped item:", draggedItem);
setDraggedItem(null);
}
};
// Keyboard navigation
const handleKeyDown = (event) => {
switch (event.key) {
case "ArrowUp":
event.preventDefault();
console.log("Navigate up");
break;
case "ArrowDown":
event.preventDefault();
console.log("Navigate down");
break;
case "Enter":
console.log("Select item");
break;
case "Escape":
console.log("Cancel action");
break;
default:
break;
}
};
const items = ["Item 1", "Item 2", "Item 3"];
return (
<div>
<h3>Draggable Items</h3>
{items.map((item, index) => (
<div
key={index}
draggable
onDragStart={(e) => handleDragStart(e, item)}
style={{
padding: "10px",
margin: "5px",
backgroundColor: "#f0f0f0",
cursor: "move"
}}
>
{item}
</div>
))}
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
style={{
width: "200px",
height: "100px",
border: "2px dashed #ccc",
backgroundColor: dropZoneActive ? "#e6f3ff" : "transparent",
display: "flex",
alignItems: "center",
justifyContent: "center",
marginTop: "20px"
}}
>
Drop Zone
</div>
<div
tabIndex="0"
onKeyDown={handleKeyDown}
style={{
padding: "20px",
border: "1px solid #ccc",
marginTop: "20px",
outline: "none"
}}
>
Focus me and use arrow keys, Enter, or Escape
</div>
</div>
);
}
Best Practices for Event Handling and Forms
- Use controlled components: For better control and validation
- Validate on both client and server: Never trust client-side validation alone
- Provide immediate feedback: Show errors as users type
- Use semantic HTML: Proper form elements and labels
- Handle loading states: Show feedback during form submission
- Prevent multiple submissions: Disable submit button during processing
- Use event delegation: For dynamic content
- Clean up event listeners: In useEffect cleanup (covered later)
Summary
Event handling and forms are essential for creating interactive React applications. You've learned about SyntheticEvents, controlled vs uncontrolled components, form validation, and advanced event handling patterns. These concepts form the foundation for building rich, interactive user interfaces. In the next lesson, we'll explore React Hooks in more depth and learn about component lifecycle management.