We have components. Now we need the user to be able to interact with the interface: clicking buttons, typing in forms, or dragging elements.
In React, event handling is very similar to traditional DOM, but with some syntactic and architectural differences that make our code more predictable and cross-browser compatible.
Syntax: React vs HTML
If you come from writing classic HTML, you’ll be used to this:
<button onclick="activateLasers()">Fire</button>
In React (JSX), there are two fundamental differences:
- CamelCase: Events are named using camelCase, not lowercase. We use
onClickinstead ofonclick,onChangeinstead ofonchange, etc. - Functions, not Strings: In JSX we pass the function itself, not a string with code to execute.
// React (JSX)
function Button() {
const handleClick = () => {
alert('Pew pew!');
};
return (
<button onClick={handleClick}>
Fire
</button>
);
}
The Common Mistake: Executing vs Referencing
Pay close attention to how we pass the function to the event:
- ✅ Correct (Pass reference):
onClick={handleClick} - ❌ Incorrect (Execute function):
onClick={handleClick()}
If you add parentheses (), you are executing the function immediately when React renders the component, not when the user clicks.
Most Common Events
Although React supports almost all browser events, 90% of the time you’ll use these two:
Used for buttons, links, and interactive elements. Works the same as native click.
<button onClick={() => setCounter(c => c + 1)}>
Increment
</button>
Fundamental for forms. Unlike in HTML, where onchange usually fires when the input loses focus (blur), in React onChange fires with every keystroke.
This is important to keep React’s state synchronized with what the user sees in real time (what we call Controlled Components, which we’ll see later).
const [text, setText] = useState('');
return (
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
);
The SyntheticEvent Object
In the previous example we used an argument e. What exactly is that e?
If you inspect that object in the console, you’ll see that it’s not a native DOM event. It’s a SyntheticEvent.
React implements its own event system. It’s an abstraction layer (a wrapper) that wraps the browser’s native event.
Why does React do this?|t291443
To guarantee Cross-Browser Compatibility.
Different browsers (Chrome, Safari, Firefox, and the old IE) sometimes implement events slightly differently. React normalizes all of this so the event interface is identical, regardless of where your web app runs.
const handleClick = (e) => {
e.preventDefault(); // Prevents default behavior
e.stopPropagation(); // Prevents event bubbling
console.log(e.target); // The element that triggered the event
console.log(e.type); // 'click'
}
If for some obscure reason you need to access the underlying native browser event, it’s available at e.nativeEvent.
Passing Arguments to Event Handlers
Often, within a list, we’ll want to pass specific data to the handler (for example, the ID of the element we want to delete).
As we said, we cannot execute the function directly. That is, we cannot do this:
// ❌ This is wrong
<button onClick={delete(id)}>
Delete
</button>
We have two options to solve this:
It’s the most common and modern way. We create an anonymous function that, when executed, calls our function with parameters.
// ✅ Correct
<button onClick={() => deleteUser(user.id)}>
Delete
</button>
Here, what we pass to onClick is the definition of the arrow function. React will execute that arrow when the click occurs, and the arrow will in turn execute deleteUser.
A more functional technique, useful if you want to avoid creating arrow functions on every render (although the performance impact is negligible).
const createHandler = (id) => () => {
deleteUser(id);
};
// Usage
<button onClick={createHandler(user.id)}>Delete</button>
Prevent Default
In HTML, if you have a form and a submit button, clicking it will reload the page. In a SPA (Single Page Application) like the ones we build with React, we want to avoid that reload.
To do this, we use e.preventDefault().
function Form() {
const handleSubmit = (e) => {
// 1. IMPORTANT: We prevent the page from reloading
e.preventDefault();
// 2. We process the data
console.log('Sending data...');
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
