😵 The Problem
You build a React component.
The UI looks fine.
Then you add a button:
<button>Save</button>
But clicking it does… absolutely nothing. 😄
So you add a function:
function handleSave() {
console.log("Saved!");
}
Then you might try:
<button onclick="handleSave()">
Save
</button>
If you've worked with normal HTML and JavaScript before, this feels completely reasonable.
But React doesn't work that way.
And this is where React Event Handling comes in.
💡 The Solution: React Event Handlers
In React, you attach event handlers directly to JSX elements.
function handleSave() {
console.log("Saved!");
}
function App() {
return (
<button onClick={handleSave}>
Save
</button>
);
}
That's it.
When the user clicks the button:
User clicks
↓
React detects the event
↓
handleSave runs
↓
Your application logic executes
The important part is:
onClick={handleSave}
You're passing the function to React.
You're not calling it yourself.
⚠️ Don't Call the Handler During Rendering
This is one of the most common beginner mistakes.
Wrong:
<button onClick={handleSave()}>
Save
</button>
That calls handleSave() immediately while React is rendering the component.
Usually, you want:
<button onClick={handleSave}>
Save
</button>
Think of it like this:
onClick={handleSave}
means:
"React, call this when the click happens."
While:
onClick={handleSave()}
means:
"Run this function right now and give the result to onClick."
That small pair of parentheses can completely change the behavior.
🔥 What About Passing Arguments?
This is where things get slightly more interesting.
Imagine you have a list of products:
const products = [
{ id: 1, name: "Laptop" },
{ id: 2, name: "Keyboard" },
{ id: 3, name: "Mouse" }
];
You want to delete a specific product.
You can't do this:
<button onClick={handleDelete(product.id)}>
Delete
</button>
Because the function runs during rendering.
Instead:
<button onClick={() => handleDelete(product.id)}>
Delete
</button>
Then:
function handleDelete(id) {
console.log("Deleting:", id);
}
The arrow function acts as a small wrapper.
React calls that wrapper when the user clicks.
Then the wrapper calls your actual function with the required argument.
🧠 React Events Aren't Just Clicks
onClick is probably the first event you'll use.
But real applications handle plenty of other interactions.
Input Changes
<input
type="text"
onChange={handleChange}
/>
Form Submission
<form onSubmit={handleSubmit}>
<button type="submit">
Login
</button>
</form>
Keyboard Events
<input
onKeyDown={handleKeyDown}
/>
Focus
<input
onFocus={handleFocus}
/>
Mouse Events
<div
onMouseEnter={handleMouseEnter}
>
Hover me
</div>
The pattern is basically the same:
Event happens
↓
React invokes your handler
↓
Handler performs some logic
🎯 The Event Object
React passes an event object to your handler.
For example:
function handleChange(event) {
console.log(event);
}
For an input, you'll commonly use:
function handleChange(event) {
console.log(event.target.value);
}
If the user types:
hello
then:
event.target.value
contains:
"hello"
This is the foundation of handling controlled inputs in React.
🔥 Real Example: Controlled Input
Let's say we're building a search box.
import { useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
function handleChange(event) {
setQuery(event.target.value);
}
return (
<input
value={query}
onChange={handleChange}
placeholder="Search products..."
/>
);
}
Now the flow is:
User types
↓
onChange fires
↓
handleChange runs
↓
setQuery updates state
↓
React renders again
↓
Input reflects the latest value
This pattern shows up everywhere:
- search boxes
- login forms
- filters
- settings pages
- checkout forms
- admin dashboards
📝 Handling Forms
Forms are another place where React event handling becomes important.
Example:
function LoginForm() {
function handleSubmit(event) {
event.preventDefault();
console.log("Form submitted");
}
return (
<form onSubmit={handleSubmit}>
<input type="email" />
<input type="password" />
<button type="submit">
Login
</button>
</form>
);
}
Why use:
event.preventDefault();
Because the browser has a default form submission behavior.
In a React application, you often want to handle the submission yourself instead of letting the browser reload the page.
So:
Browser default behavior
↓
preventDefault()
↓
React handles submission
Very common pattern.
🔄 Event Handling + State
This is where React events become really useful.
Consider a counter:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleIncrement() {
setCount(prev => prev + 1);
}
return (
<div>
<h2>{count}</h2>
<button onClick={handleIncrement}>
+1
</button>
</div>
);
}
The event itself doesn't change the UI.
The event triggers a state update.
Then React renders the updated UI.
That's an important mental model:
Events trigger logic. State changes drive the UI.
💡 Real Developer Insight
Here's something that becomes obvious when working on larger React applications:
The event handler itself usually isn't the hard part.
The problem is what you put inside it.
This is technically valid:
function handleSubmit(event) {
event.preventDefault();
// validate form
// transform data
// call API
// update state
// show notification
// navigate
// track analytics
// handle errors
}
But now one event handler is responsible for everything.
Six months later, you're debugging a 150-line handleSubmit() function. 😅
A better approach is to keep the handler focused on the interaction:
async function handleSubmit(event) {
event.preventDefault();
const data = getFormData();
if (!validateForm(data)) {
return;
}
await saveUser(data);
}
Now you can understand the flow almost immediately.
The detailed logic can live in separate functions or modules.
Good event handlers coordinate work. They shouldn't become the entire application.
⚠️ Common Developer Mistakes
1. Calling Event Handlers Immediately
Wrong:
<button onClick={handleDelete(id)}>
Delete
</button>
Correct:
<button onClick={() => handleDelete(id)}>
Delete
</button>2. Using onclick Instead of onClick
React uses camelCase event names:
onClick
onChange
onSubmit
onKeyDown
Not:
onclick
onchange
onsubmit
3. Forgetting preventDefault()
When manually handling form submission:
function handleSubmit(event) {
event.preventDefault();
}
Otherwise the browser's default behavior can interfere with your React flow.
4. Putting Too Much Logic Inside JSX
This:
<button
onClick={() => {
validate();
save();
notify();
navigate();
}}
>
Save
</button>
might be okay for tiny logic.
But once it grows, extract it:
<button onClick={handleSave}>
Save
</button>
Cleaner.
Easier to test.
Easier to maintain.
5. Forgetting That Events Can Update State
If your handler calls:
setCount(...)
React schedules a re-render.
The current render's state value doesn't magically change halfway through the function.
When the next state depends on the previous state, prefer:
setCount(prev => prev + 1);
It's a small habit that prevents a lot of confusing state-update bugs.
🚀 Best Practice Summary
🎯 Conclusion
React Event Handling isn't really complicated.
The syntax is just slightly different from traditional JavaScript:
onClick={handleClick}
But the important part is understanding what happens after the event.
A good mental model is:
User interaction
↓
React event
↓
Event handler
↓
State / application logic
↓
UI update
Once you understand that flow, buttons, forms, inputs, keyboard events, and interactive components all start feeling like variations of the same thing.
And one rule is worth remembering:
Keep your event handlers simple enough that another developer can understand the user action just by reading the function.
That's what makes React code easier to maintain as the application grows. 🚀
0 Comments