100% Private • No tracking100% Client-Side•No data leaves your browser•No accounts•Works offline
Learn how to handle user interactions in React through event handlers and create responsive, interactive components.
Event handling is how React components respond to user interactions. React uses a unified event system called SyntheticEvents that works consistently across all browsers and provides a clean API for handling user input.
React wraps native DOM events in SyntheticEvent objects that:
function Button() {
// Event handler function
const handleClick = (event) => {
console.log('Button clicked!');
console.log('Event object:', event);
};
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
// Inline event handler (for simple actions)
function SimpleButton() {
return (
<button onClick={() => console.log('Clicked!')}>
Click me!
</button>
);
}
function ClickExample() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('');
const handleClick = () => {
setCount(count + 1);
setMessage(`Button clicked \${count + 1} times!`);
};
const handleDoubleClick = () => {
setMessage('Double clicked!');
setCount(count + 2);
};
const handleRightClick = (event) => {
event.preventDefault(); // Prevent context menu
setMessage('Right clicked!');
};
return (
<div>
<h3>Click Events Demo</h3>
<p>{message}</p>
<p>Count: {count}</p>
<button onClick={handleClick}>
Single Click (+1)
</button>
<button onDoubleClick={handleDoubleClick}>
Double Click (+2)
</button>
<button onContextMenu={handleRightClick}>
Right Click (no menu)
</button>
</div>
);
}
function InputExample() {
const [text, setText] = useState('');
const [email, setEmail] = useState('');
const [number, setNumber] = useState(0);
const handleTextChange = (event) => {
setText(event.target.value);
};
const handleEmailChange = (event) => {
setEmail(event.target.value);
};
const handleNumberChange = (event) => {
setNumber(parseInt(event.target.value) || 0);
};
return (
<div>
<h3>Input Events Demo</h3>
<div className="form-group">
<label>Text Input:</label>
<input
type="text"
value={text}
onChange={handleTextChange}
placeholder="Type something..."
/>
<p>You typed: {text}</p>
<p>Character count: {text.length}</p>
</div>
<div className="form-group">
<label>Email Input:</label>
<input
type="email"
value={email}
onChange={handleEmailChange}
placeholder="Enter email..."
/>
<p>Email: {email}</p>
<p>Valid email: {email.includes('@') ? 'Yes' : 'No'}</p>
</div>
<div className="form-group">
<label>Number Input:</label>
<input
type="number"
value={number}
onChange={handleNumberChange}
/>
<p>Number: {number}</p>
<p>Double: {number * 2}</p>
</div>
</div>
);
}
function FormControls() {
const [selectedOption, setSelectedOption] = useState('');
const [isChecked, setIsChecked] = useState(false);
const [selectedCheckboxes, setSelectedCheckboxes] = useState([]);
const [selectedRadio, setSelectedRadio] = useState('');
const handleSelectChange = (event) => {
setSelectedOption(event.target.value);
};
const handleCheckboxChange = (event) => {
setIsChecked(event.target.checked);
};
const handleMultiCheckboxChange = (value) => {
setSelectedCheckboxes(prev => {
if (prev.includes(value)) {
return prev.filter(item => item !== value);
} else {
return [...prev, value];
}
});
};
const handleRadioChange = (event) => {
setSelectedRadio(event.target.value);
};
return (
<div>
<h3>Form Controls Demo</h3>
{/* Select Dropdown */}
<div className="form-group">
<label>Choose a color:</label>
<select value={selectedOption} onChange={handleSelectChange}>
<option value="">-- Select a color --</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
</select>
<p>Selected: {selectedOption}</p>
</div>
{/* Single Checkbox */}
<div className="form-group">
<label>
<input
type="checkbox"
checked={isChecked}
onChange={handleCheckboxChange}
/>
Subscribe to newsletter
</label>
<p>Subscribed: {isChecked ? 'Yes' : 'No'}</p>
</div>
{/* Multiple Checkboxes */}
<div className="form-group">
<label>Select your interests:</label>
{['React', 'JavaScript', 'CSS', 'Node.js'].map(interest => (
<label key={interest}>
<input
type="checkbox"
checked={selectedCheckboxes.includes(interest)}
onChange={() => handleMultiCheckboxChange(interest)}
/>
{interest}
</label>
))}
<p>Selected: {selectedCheckboxes.join(', ')}</p>
</div>
{/* Radio Buttons */}
<div className="form-group">
<label>Choose your experience level:</label>
{['Beginner', 'Intermediate', 'Advanced'].map(level => (
<label key={level}>
<input
type="radio"
name="experience"
value={level}
checked={selectedRadio === level}
onChange={handleRadioChange}
/>
{level}
</label>
))}
<p>Experience: {selectedRadio}</p>
</div>
</div>
);
}
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
newsletter: false
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitMessage, setSubmitMessage] = useState('');
const handleChange = (event) => {
const { name, value, type, checked } = event.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const validateForm = () => {
const newErrors = {};
if (!formData.name.trim()) {
newErrors.name = '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.message.trim()) {
newErrors.message = 'Message is required';
} else if (formData.message.length < 10) {
newErrors.message = 'Message must be at least 10 characters';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (event) => {
event.preventDefault(); // Prevent page reload
if (!validateForm()) {
return;
}
setIsSubmitting(true);
setSubmitMessage('');
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
setSubmitMessage('Message sent successfully!');
setFormData({
name: '',
email: '',
message: '',
newsletter: false
});
} catch (error) {
setSubmitMessage('Error sending message. Please try again.');
} finally {
setIsSubmitting(false);
}
};
return (
<div>
<h3>Contact Form</h3>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Name:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
className={errors.name ? 'error' : ''}
/>
{errors.name && <span className="error-message">{errors.name}</span>}
</div>
<div className="form-group">
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className={errors.email ? 'error' : ''}
/>
{errors.email && <span className="error-message">{errors.email}</span>}
</div>
<div className="form-group">
<label>Message:</label>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
rows="4"
className={errors.message ? 'error' : ''}
/>
{errors.message && <span className="error-message">{errors.message}</span>}
</div>
<div className="form-group">
<label>
<input
type="checkbox"
name="newsletter"
checked={formData.newsletter}
onChange={handleChange}
/>
Subscribe to newsletter
</label>
</div>
<button
type="submit"
disabled={isSubmitting}
className="submit-button"
>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
{submitMessage && (
<p className={submitMessage.includes('Error') ? 'error-message' : 'success-message'}>
{submitMessage}
</p>
)}
</form>
{/* Debug info */}
<div className="debug-info">
<h4>Form Data:</h4>
<pre>{JSON.stringify(formData, null, 2)}</pre>
</div>
</div>
);
}
function KeyboardExample() {
const [pressedKey, setPressedKey] = useState('');
const [inputValue, setInputValue] = useState('');
const [keyHistory, setKeyHistory] = useState([]);
const handleKeyDown = (event) => {
setPressedKey(`Key Down: \${event.key}`);
// Handle special keys
if (event.key === 'Enter') {
console.log('Enter pressed!');
}
if (event.key === 'Escape') {
setInputValue('');
console.log('Input cleared!');
}
// Handle key combinations
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Save shortcut pressed!');
}
};
const handleKeyUp = (event) => {
setPressedKey(`Key Up: \${event.key}`);
};
const handleKeyPress = (event) => {
// Note: keypress is deprecated, use keydown instead
setKeyHistory(prev => [...prev.slice(-9), event.key]);
};
const handleInputKeyDown = (event) => {
if (event.key === 'Enter') {
alert(`You typed: \${inputValue}`);
}
};
return (
<div>
<h3>Keyboard Events Demo</h3>
<div className="keyboard-area">
<p>Click here and press any key:</p>
<div
tabIndex="0" // Makes div focusable
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
style={{
border: '2px solid #ccc',
padding: '20px',
backgroundColor: '#f9f9f9',
outline: 'none'
}}
>
Focus this area and press keys
</div>
<p>{pressedKey}</p>
</div>
<div className="input-area">
<label>Type and press Enter:</label>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleInputKeyDown}
placeholder="Press Enter to alert"
/>
</div>
<div className="key-history">
<h4>Last 10 keys pressed:</h4>
<p>{keyHistory.join(' → ')}</p>
</div>
<div className="shortcuts">
<h4>Try these shortcuts:</h4>
<ul>
<li>Ctrl+S: Save (prevented)</li>
<li>Escape: Clear input</li>
<li>Enter: Submit input</li>
</ul>
</div>
</div>
);
}
function MouseExample() {
const [mouseInfo, setMouseInfo] = useState({
x: 0,
y: 0,
isOver: false,
isDown: false
});
const [clickCount, setClickCount] = useState(0);
const handleMouseMove = (event) => {
setMouseInfo(prev => ({
...prev,
x: event.clientX,
y: event.clientY
}));
};
const handleMouseEnter = () => {
setMouseInfo(prev => ({ ...prev, isOver: true }));
};
const handleMouseLeave = () => {
setMouseInfo(prev => ({ ...prev, isOver: false, isDown: false }));
};
const handleMouseDown = () => {
setMouseInfo(prev => ({ ...prev, isDown: true }));
};
const handleMouseUp = () => {
setMouseInfo(prev => ({ ...prev, isDown: false }));
setClickCount(prev => prev + 1);
};
const handleDragStart = (event) => {
event.dataTransfer.setData('text/plain', 'Dragged item');
};
const handleDrop = (event) => {
event.preventDefault();
const data = event.dataTransfer.getData('text/plain');
console.log('Dropped:', data);
};
const handleDragOver = (event) => {
event.preventDefault(); // Allow drop
};
return (
<div>
<h3>Mouse Events Demo</h3>
<div
className="mouse-area"
onMouseMove={handleMouseMove}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
style={{
width: '300px',
height: '200px',
border: '2px solid #333',
backgroundColor: mouseInfo.isOver ? '#e6f3ff' : '#f9f9f9',
cursor: mouseInfo.isDown ? 'grabbing' : 'grab',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none'
}}
>
<div>
<p>Mouse Position: ({mouseInfo.x}, {mouseInfo.y})</p>
<p>Mouse Over: {mouseInfo.isOver ? 'Yes' : 'No'}</p>
<p>Mouse Down: {mouseInfo.isDown ? 'Yes' : 'No'}</p>
<p>Click Count: {clickCount}</p>
</div>
</div>
<div className="drag-drop-demo">
<h4>Drag and Drop:</h4>
<div
draggable
onDragStart={handleDragStart}
style={{
padding: '10px',
backgroundColor: '#4CAF50',
color: 'white',
display: 'inline-block',
cursor: 'grab',
margin: '10px'
}}
>
Drag me!
</div>
<div
onDrop={handleDrop}
onDragOver={handleDragOver}
style={{
width: '200px',
height: '100px',
border: '2px dashed #ccc',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '10px'
}}
>
Drop zone
</div>
</div>
</div>
);
}
function ButtonList() {
const [selectedId, setSelectedId] = useState(null);
const items = [
{ id: 1, name: 'Item 1', color: 'red' },
{ id: 2, name: 'Item 2', color: 'blue' },
{ id: 3, name: 'Item 3', color: 'green' }
];
// Method 1: Arrow function in JSX
const handleClick1 = (id, name) => {
setSelectedId(id);
console.log(`Clicked \${name}`);
};
// Method 2: Curried function
const handleClick2 = (id, name) => (event) => {
setSelectedId(id);
console.log(`Clicked \${name}`, event);
};
// Method 3: Data attributes
const handleClick3 = (event) => {
const id = parseInt(event.target.dataset.id);
const name = event.target.dataset.name;
setSelectedId(id);
console.log(`Clicked \${name}`);
};
return (
<div>
<h3>Event Handlers with Parameters</h3>
<p>Selected ID: {selectedId}</p>
<div>
<h4>Method 1: Arrow function in JSX</h4>
{items.map(item => (
<button
key={item.id}
onClick={() => handleClick1(item.id, item.name)}
style={{ backgroundColor: item.color, margin: '5px' }}
>
{item.name}
</button>
))}
</div>
<div>
<h4>Method 2: Curried function</h4>
{items.map(item => (
<button
key={item.id}
onClick={handleClick2(item.id, item.name)}
style={{ backgroundColor: item.color, margin: '5px' }}
>
{item.name}
</button>
))}
</div>
<div>
<h4>Method 3: Data attributes</h4>
{items.map(item => (
<button
key={item.id}
data-id={item.id}
data-name={item.name}
onClick={handleClick3}
style={{ backgroundColor: item.color, margin: '5px' }}
>
{item.name}
</button>
))}
</div>
</div>
);
}
function EventDelegation() {
const [clickedItem, setClickedItem] = useState('');
const handleContainerClick = (event) => {
// Check if clicked element is a button
if (event.target.tagName === 'BUTTON') {
const action = event.target.dataset.action;
const value = event.target.textContent;
setClickedItem(`Action: \${action}, Value: \${value}`);
}
};
return (
<div>
<h3>Event Delegation Example</h3>
<p>Clicked: {clickedItem}</p>
{/* Single event handler for multiple buttons */}
<div onClick={handleContainerClick}>
<button data-action="save">Save</button>
<button data-action="delete">Delete</button>
<button data-action="edit">Edit</button>
<button data-action="cancel">Cancel</button>
</div>
</div>
);
}
function SearchInput() {
const [query, setQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
// Debounce search to avoid too many API calls
useEffect(() => {
if (!query.trim()) {
setSearchResults([]);
return;
}
setIsSearching(true);
const timeoutId = setTimeout(async () => {
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500));
// Mock search results
const results = [
`Result for "\${query}" #1`,
`Result for "\${query}" #2`,
`Result for "\${query}" #3`
];
setSearchResults(results);
} catch (error) {
console.error('Search failed:', error);
} finally {
setIsSearching(false);
}
}, 300); // 300ms delay
return () => clearTimeout(timeoutId);
}, [query]);
return (
<div>
<h3>Debounced Search</h3>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{isSearching && <p>Searching...</p>}
<ul>
{searchResults.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
}
function LinkExample() {
const handleLinkClick = (event) => {
event.preventDefault(); // Don't navigate
console.log('Link clicked but navigation prevented');
};
const handleContainerClick = () => {
console.log('Container clicked');
};
const handleButtonClick = (event) => {
event.stopPropagation(); // Don't bubble to container
console.log('Button clicked');
};
return (
<div onClick={handleContainerClick} style={{ padding: '20px', border: '1px solid #ccc' }}>
<p>Container (click anywhere)</p>
<a href="https://example.com" onClick={handleLinkClick}>
Prevented Link
</a>
<button onClick={handleButtonClick}>
Button (stops propagation)
</button>
</div>
);
}
function AccessibleButton() {
const [isPressed, setIsPressed] = useState(false);
const handleActivation = () => {
setIsPressed(!isPressed);
console.log('Button activated');
};
const handleKeyDown = (event) => {
// Handle Enter and Space keys for accessibility
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleActivation();
}
};
return (
<div
role="button"
tabIndex="0"
aria-pressed={isPressed}
onClick={handleActivation}
onKeyDown={handleKeyDown}
style={{
padding: '10px 20px',
backgroundColor: isPressed ? '#007bff' : '#f8f9fa',
color: isPressed ? 'white' : 'black',
border: '1px solid #ccc',
cursor: 'pointer',
outline: 'none'
}}
>
{isPressed ? 'Pressed' : 'Not Pressed'}
</div>
);
}
function PerformantList() {
const [items, setItems] = useState(
Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item \${i}` }))
);
// ❌ Bad - creates new function on every render
const badExample = () => {
return items.map(item => (
<div key={item.id} onClick={() => console.log(item.id)}>
{item.name}
</div>
));
};
// ✅ Good - stable function reference
const handleItemClick = useCallback((event) => {
const id = parseInt(event.target.dataset.id);
console.log('Clicked item:', id);
}, []);
return (
<div onClick={handleItemClick}>
{items.slice(0, 10).map(item => (
<div key={item.id} data-id={item.id}>
{item.name}
</div>
))}
</div>
);
}
// ❌ Wrong - calls function immediately
<button onClick={handleClick()}>Click me</button>
// ✅ Correct - passes function reference
<button onClick={handleClick}>Click me</button>
// ✅ Correct - arrow function for parameters
<button onClick={() => handleClick(id)}>Click me</button>
// ❌ Problematic with class components
class MyComponent extends Component {
handleClick() {
console.log(this); // undefined in strict mode
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
// ✅ Solutions for class components
class MyComponent extends Component {
// Method 1: Arrow function
handleClick = () => {
console.log(this); // correctly bound
}
// Method 2: Bind in constructor
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
// Method 3: Arrow function in JSX
render() {
return <button onClick={() => this.handleClick()}>Click</button>;
}
}
Event handling is essential for creating interactive React components:
✅ Use consistent event handler naming (handleEventName)
✅ Prevent default behavior when needed with preventDefault()
✅ Stop event bubbling with stopPropagation() when appropriate
✅ Handle keyboard events for accessibility
✅ Use controlled components for form inputs
✅ Consider performance with large lists and frequent events
In the next lesson, we'll explore Conditional Rendering - how to show or hide content based on state and props. You'll learn:
Conditional rendering lets you create dynamic UIs that respond to user interactions and application state!