Like many React developers, you've probably struggled with event handlers in your apps. Trust me, making sure everything works smoothly while keeping your code clean can be tricky.
Picture this: your users click a button and... nothing happens π₯. Or even worse, they get an error message! That's super frustrating for them and can make your app look bad.
But don't worry! I'll show you how to handle events in React like a pro. By the end of this guide, you'll know exactly how to create apps that respond perfectly to user actions, making them want to come back for more.
What is an event
As the user interacts with the page, hundreds of events are fired off in response. The browser is like an invasive private investigator, tracking every little thing you do.
When we're building dynamic web applications, these events become super important. We'll listen for these events, and use them to trigger state changes. When the user clicks the "X" button, we dismiss the prompt. When the user submits the form, we show a loading spinner.
How events work
In order to respond to an event, we need to listen for it. JavaScript provides a built-in way to do this, with the addEventListener
method:
const button = document.querySelector('.btn');
function doSomething() {
// Stuff here
}
button.addEventListener('click', doSomething);
In this code, we're listening for a specific event (clicks) targeting a specific element (.btn
). We have a function which is meant to handle this event, doSomething
. When the user clicks this particular button, our handler function will be invoked, allowing us to do something in response.
The web platform offers another way to do this as well. We can embed our handler right in the HTML:
<button onclick="doSomething()">
Click me!
</button>
React bases itself on this pattern, allowing us to pass an event handler right in the JSX:
function App() {
function doSomething() {
// Stuff here
}
return (
<button onClick={doSomething}>
Click me!
</button>
);
}
As with addEventListener
, this code will perform the same duty: when the user clicks the button, the doSomething
function will be called.
βοΈ This is the recommended way to handle events in React!
While we do sometimes have to use addEventListener
for window-level events (covered in Module 3), we should try to use the βon Xβ props like onClick
and onChange
whenever possible.
There are a few good reasons why:
β Automatic clean-up: Whenever we add an event listener, we're also supposed to remove it when we're done, with removeEventListener
. If we forget to do this, we'll introduce a memory leak* β React automatically removes listeners for us when we use βon Xβ handler functions.
β Improved performance: By giving React control over the event listeners, it can optimize things for us, like batching multiple event listeners together to reduce memory consumption.
β No DOM interaction: React likes for us to stay within its abstraction. We generally try to avoid interacting with the DOM directly. In order to use addEventListener
, we have to look up the element with querySelector
. This is something we should avoid. The βhappy pathβ in React involves letting React do the DOM manipulation for us.
One of the core ideas behind React is that it does the DOM manipulation for you. When using React, you shouldn't really be using querySelector at all. We want to stay within React's abstraction, rather than trying to compete with it to manage the DOM.
Differences from HTML
When we look at the onChange
prop, it looks very similar to the βin HTMLβ method of adding event handlers. There are some key differences, though.
Camel Casing
We need to write βcamelCasedβ attribute names in JSX. Be careful to write onClick
, with a capital βCβ, instead of onclick
. Same thing with onChange
, onKeyDown
, onTransitionEnd
, etc.
Here's the good news: if you forget, you'll get a helpful console warning letting you know about your mistake:
Warning: Invalid event handler property onclick. Did you mean onClick?
This common mistake tends to be pretty easy to spot. Let's look at another common issue that, unfortunately, is harder to spot.
Passing a function reference
When working with event handlers in React, we need to pass a reference to the function. We don't call the function, like we do in HTML:
// β
We want to do this:
<button onClick={doSomething}/>
// π« Not this:
<button onClick={doSomething()}/>
When we include the parentheses, we invoke the function right away, the moment the React application is rendered. We would rather not do that; we want to give React a reference to the function, so that React can call the function at a later time when the user clicks on the button.
In case JSX is getting in the way, here's the same code written in compiled JavaScript:
// β
Correct.
// Will be called when the user clicks the button.
React.createElement(
'button',
{
onClick: doSomething,
}
);
// π« Incorrect
// Will be called immediately, when the element is created.
React.createElement(
'button',
{
onClick: doSomething(),
}
);
Specifying arguments
For example, let's suppose that our function is called setTheme
, and we'll use it to change the user's color theme from Light Mode to Dark Mode.
In order for this to work, we need to supply the name of the theme we're switching to, like this:
// Switch to light mode:
setTheme('light');
// Switch to dark mode:
setTheme('dark');
How do we do this, though?
If we pass setTheme
as a reference to onClick
, we can't supply arguments:
// π« Invalid: calls the function without 'dark'
<button onClick={setTheme}>
Toggle theme
</button>
In order to specify the argument, we need to wrap it in parentheses, but then it gets called right away:
// π« Invalid: calls the function right away
<button onClick={setTheme('dark')}>
Toggle theme
</button>
We can solve this problem with a wrapper function:
// β
Valid:
<button onClick={()=>setTheme('dark')}>
Toggle theme
</button>
We're creating a brand-new anonymous arrow function, () => setTheme('dark')
, and passing it to React. When the user clicks the button, the function will run, and the code inside the function is executed (setTheme('dark')
).
If it's still confusing, there's no need to worry: this pattern is very common in React, and there will be ample opportunity in this course for you to become familiar and comfortable with it!
β There isn't a measurable performance difference between creating anonymous functions vs. named functions. Same thing for arrow vs. traditional functions.
β The cost of creating a new function is extremely low. Even low-end devices can create hundreds of thousands of functions in the span of a human blink.
β React has a built-in event delegation system that implements a bunch of optimizations for us.
What about .bind()
There is another way we could have solved this problem: using the function bind
method:
// β
Valid:
<button onClick={setTheme.bind(null,'dark')}>
Toggle theme
</button>
While this method works just fine, it's much less conventional in the React community, and there's no clear advantage. I suggest using anonymous wrapper functions instead.
π. Similar posts
How to Handle Asynchronous Updates with useState in Your React App
30 Jan 2025
The Simple Guide to React Range Utility for Effective Coding
26 Jan 2025
Everything You Need to Know About React Conditional Logic
22 Jan 2025