Topic 6: Events
📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: lifecycle-methods, state-and-props
⚠️ Note: this lesson was regenerated because the source content did not match the title.
Why this matters
Up until now, your React components have just been displaying things — text, buttons, forms. But none of it does anything when a user clicks or types. That's what events fix. The moment you understand React's event system, your UI stops being a poster and starts being an app. We're talking about responding to clicks, capturing what someone types into a field, and handling form submissions — the exact things users expect to happen the second they interact with your page.
What You'll Learn
- How React's synthetic event system wraps native browser events
- How to attach event handlers to JSX elements using
onClick,onChange,onSubmit, and more - How to prevent default browser behavior and stop event propagation
- How to pass arguments to event handlers without immediately invoking them
The Analogy
Think of Vizag's public suggestion box. Every citizen who walks up and drops a note is like a browser event — a click, a keypress, a form submission. The class aide standing beside the box is React's synthetic event system: they intercept every note, translate it into a standardized format that all students can read regardless of which district (browser) it came from, and then route it to the right department. The aide can also decide to shred a note before it reaches the mayor (preventing default behavior) or stop it from being passed up the chain of command (stopping propagation). Your event handler is simply the department that finally acts on the note.
Chapter 1: Synthetic Events
React doesn't hand you raw browser events. Instead it wraps them in a SyntheticEvent — a cross-browser abstraction that normalizes inconsistencies between Chrome, Firefox, Safari, and Edge so your handlers work identically everywhere.
Key properties you'll use on every SyntheticEvent:
| Property / Method | What it gives you |
|---|---|
event.target | The DOM element that triggered the event |
event.target.value | The current value of an input element |
event.type | The event type string, e.g. "click" |
event.preventDefault() | Stops the browser's built-in action |
event.stopPropagation() | Stops the event from bubbling to parent elements |
function LogEvent() {
function handleClick(event) {
console.log(event.type); // "click"
console.log(event.target); // <button>...</button>
}
return <button onClick={handleClick}>Inspect Me</button>;
}
React reuses SyntheticEvent objects for performance. If you need to access event properties asynchronously (e.g. inside a
setTimeout), callevent.persist()first — or destructure the values you need before the async call.
Chapter 2: Attaching Event Handlers
In JSX, event handlers are camelCase attributes that accept a function reference — not a function call. Passing onClick={handleClick} hands React the function to call later. Passing onClick={handleClick()} calls it immediately during render, which is almost never what you want.
// Correct — pass the reference
<button onClick={handleClick}>Click me</button>
// Wrong — invokes immediately on every render
<button onClick={handleClick()}>Click me</button>
You can define handlers inline with an arrow function when the logic is short:
<button onClick={() => console.log('Clicked!')}>Click me</button>
Or as a named method on a class component:
class Counter extends React.Component {
handleIncrement() {
this.setState((prev) => ({ count: prev.count + 1 }));
}
render() {
return (
<button onClick={this.handleIncrement.bind(this)}>
Count: {this.state.count}
</button>
);
}
}
In class components, always bind handlers in the constructor or use class property arrow functions to ensure
thisis correct.
Chapter 3: Common Event Types
React exposes event handlers for the full spectrum of DOM events. Here are the ones you'll reach for most:
Mouse Events
<button onClick={handleClick}>Click</button>
<div onDoubleClick={handleDoubleClick}>Double-click me</div>
<div onMouseEnter={handleHover}>Hover zone</div>
<div onMouseLeave={handleLeave}>Leave zone</div>
Keyboard Events
<input
onKeyDown={(e) => console.log('Key down:', e.key)}
onKeyUp={(e) => console.log('Key up:', e.key)}
/>
Form / Input Events
<input onChange={(e) => setValue(e.target.value)} />
<form onSubmit={handleSubmit}>...</form>
<select onChange={(e) => setChoice(e.target.value)}>...</select>
<textarea onChange={(e) => setText(e.target.value)} />
Focus Events
<input onFocus={() => setActive(true)} onBlur={() => setActive(false)} />
Clipboard Events
<input onCopy={handleCopy} onPaste={handlePaste} />
Chapter 4: Handling Forms
Form handling is one of the most common event patterns in React. The standard approach is a controlled component: you store the input value in state and update it on every onChange.
import { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
function handleSubmit(event) {
event.preventDefault(); // stop the page from reloading
console.log({ email, password }); // use the values
}
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>
);
}
event.preventDefault() on line 9 is critical — without it, the browser performs a full-page navigation on every form submit.
Chapter 5: Preventing Default Behavior and Stopping Propagation
Preventing default stops the browser from executing its built-in response to an event.
// Prevent navigation when clicking an anchor
<a href="https://example.com" onClick={(e) => e.preventDefault()}>
Stay here
</a>
Stopping propagation prevents the event from bubbling up to parent elements. This matters when a child and a parent both have handlers for the same event.
function Card() {
function handleCardClick() {
console.log('Card clicked');
}
function handleButtonClick(e) {
e.stopPropagation(); // the Card's onClick will NOT fire
console.log('Button clicked');
}
return (
<div onClick={handleCardClick} style={{ padding: '20px', border: '1px solid #ccc' }}>
<p>Clicking here triggers handleCardClick.</p>
<button onClick={handleButtonClick}>Click only the button</button>
</div>
);
}
Without stopPropagation(), clicking the button fires both handleButtonClick and handleCardClick.
Chapter 6: Passing Arguments to Event Handlers
When you need to pass extra data to a handler (like an item ID from a list), wrap the call in an arrow function:
function ItemList({ items }) {
function handleDelete(id) {
console.log('Deleting item', id);
}
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button onClick={() => handleDelete(item.id)}>Delete</button>
</li>
))}
</ul>
);
}
The arrow function () => handleDelete(item.id) creates a new function reference per render. For large lists where this becomes a performance concern, consider useCallback — but don't optimize prematurely.
You can also use data-* attributes and read from event.target.dataset to avoid closures altogether:
<button data-id={item.id} onClick={handleDelete}>Delete</button>
function handleDelete(e) {
const id = e.target.dataset.id;
console.log('Deleting item', id);
}
Chapter 7: Events in Function Components with Hooks
Modern React code uses function components and hooks. Here's a complete counter that demonstrates onClick, onChange, and onKeyDown together:
import { useState } from 'react';
function SmartCounter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
function handleIncrement() {
setCount((prev) => prev + step);
}
function handleDecrement() {
setCount((prev) => prev - step);
}
function handleStepChange(e) {
const value = parseInt(e.target.value, 10);
if (!isNaN(value) && value > 0) setStep(value);
}
function handleKeyDown(e) {
if (e.key === 'ArrowUp') handleIncrement();
if (e.key === 'ArrowDown') handleDecrement();
}
return (
<div onKeyDown={handleKeyDown} tabIndex={0}>
<h2>Count: {count}</h2>
<button onClick={handleDecrement}>− {step}</button>
<button onClick={handleIncrement}>+ {step}</button>
<label>
Step size:
<input type="number" value={step} onChange={handleStepChange} min="1" />
</label>
<p>Focus the box and use ↑ / ↓ keys to step.</p>
</div>
);
}
flowchart TD
U([User Action]) --> SE[SyntheticEvent Created]
SE --> H{Handler attached?}
H -- Yes --> FN[Your handler function runs]
H -- No --> BUB[Event bubbles to parent]
FN --> PD{event.preventDefault\ncalled?}
PD -- Yes --> STOP[Browser default blocked]
PD -- No --> DEF[Browser default runs]
FN --> SP{event.stopPropagation\ncalled?}
SP -- Yes --> NOBUB[Bubbling halted]
SP -- No --> BUB
🧪 Try It Yourself
Task: Build a controlled search box that filters a hard-coded list of Vizag citizens in real time as you type.
Success criterion: Typing "bi" should show only citizens whose names contain "bi" (case-insensitive). The list updates on every keystroke with no submit button needed.
Starter snippet:
import { useState } from 'react';
const CITIZENS = ['the trainer', 'the trainer', 'the trainer', 'the trainer', 'the trainer'];
export default function CitizenSearch() {
const [query, setQuery] = useState('');
const filtered = CITIZENS.filter((name) =>
name.toLowerCase().includes(query.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="Search citizens..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{filtered.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
</div>
);
}
Add a Clear button that resets the query to an empty string when clicked.
🔍 Checkpoint Quiz
Q1. Why does React use SyntheticEvents instead of native browser events?
A) To make events faster by skipping the DOM entirely
B) To provide a normalized, cross-browser consistent event interface
C) Because native events don't support target.value
D) To enforce immutability on event data
Q2. Given this code, what gets logged when the button is clicked?
function Parent() {
return (
<div onClick={() => console.log('parent')}>
<button onClick={(e) => { e.stopPropagation(); console.log('child'); }}>
Click
</button>
</div>
);
}
A) parent, then child
B) child, then parent
C) Only child
D) Only parent
Q3. What is wrong with this handler attachment?
<button onClick={handleSave()}>Save</button>
A) handleSave must be an arrow function
B) handleSave() is called immediately on every render, not on click
C) onClick is spelled incorrectly; it should be on-click
D) Nothing is wrong; this is valid React
Q4. You have a list of 50 product cards. Each card has a Delete button that should pass the product's id to a handleDelete(id) function. How do you pass the id without invoking the function immediately?
A) onClick={handleDelete(product.id)}
B) onClick={() => handleDelete(product.id)}
C) onClick={handleDelete.bind(product.id)}
D) onClick="handleDelete(product.id)"
A1. B — SyntheticEvents normalize browser differences so your code doesn't need conditionals for each browser's quirks.
A2. C — stopPropagation() prevents the click from reaching the parent div, so only "child" is logged.
A3. B — Adding () invokes handleSave during rendering and passes its return value (likely undefined) to onClick instead of the function itself.
A4. B — Wrapping in an arrow function () => handleDelete(product.id) creates a function reference that React calls on click, passing product.id at that moment.
🪞 Recap
- React wraps native browser events in SyntheticEvents for cross-browser consistency.
- Attach handlers in JSX with camelCase props like
onClick,onChange, andonSubmit— always pass a function reference, not a call. - Call
event.preventDefault()to block browser defaults (e.g. page reload on form submit) andevent.stopPropagation()to halt event bubbling. - Controlled form inputs keep their value in state and update it via
onChange, giving you full control over the form data. - Pass arguments to handlers by wrapping them in an arrow function:
onClick={() => handleDelete(item.id)}.
📚 Further Reading
- React Docs — Responding to Events — the authoritative guide with interactive sandboxes
- MDN — Introduction to events — deep dive into how browser events work under the hood
- React Docs — Forms — controlled inputs and the full form event API
- ⬅️ Previous: State and Props
- ➡️ Next: React Lists