Handling Events
Learn how to make your React components respond to clicks, keystrokes, and input changes by wiring up event handlers the right way.
A component that never responds to the user is really just a fancy brochure. The magic of interactive UIs — buttons that do things, inputs that react as you type, forms that submit — all comes down to event handling. In this lesson you'll learn how React lets you listen for user actions, respond to them with functions, and ultimately use them to drive state changes. By the end, clicking a button will feel like a superpower you actually understand.
#Events in HTML vs. React
If you've seen a little HTML before, you may have noticed attributes like onclick on buttons. React has a very similar idea, but with two key differences:
- The attribute names are camelCase —
onClickinstead ofonclick,onChangeinstead ofonchange. - You pass a JavaScript function, not a string of code.
Here is the simplest possible example:
function HelloButton() {
function handleClick() {
alert('Hello from React!');
}
return <button onClick={handleClick}>Say Hello</button>;
}#The Golden Rule: Pass the Function, Don't Call It
This is the single most common mistake beginners make, and it trips up experienced developers too. When you wire up an event handler, you hand React the function so React can call it later when the event happens. If you call it yourself right now, it runs immediately — before any click has occurred.
Handing Over Your Phone Number vs. Calling Right Now
Imagine a friend asks, "Can someone call me when the pizza arrives?"
- Correct: You hand them your phone number. They call you when the pizza arrives.
- Wrong: You call them right now, before the pizza is even ordered.
onClick={handleClick} is handing over your number. onClick={handleClick()} is calling immediately — and React will run it during render, not on click.
// CORRECT: pass the function reference
<button onClick={handleClick}>Click me</button>
// WRONG: calling the function immediately during render
<button onClick={handleClick()}>Click me</button>The Invisible Bug
If you write onClick={handleClick()}, your component may seem to work at first — but you'll notice it fires on every render, or causes an infinite loop if it updates state. React won't throw an error; the code just does the wrong thing silently. Always double-check for stray parentheses.
#The Event Object
When React calls your handler, it passes along an event object — a bundle of information about what just happened. For a click, you can read where the mouse was. For a keystroke, you can read which key was pressed. For an input change, the most useful property is event.target.value — the current text in the field.
You can name this parameter anything, but e or event are the conventions:
function ClickLogger() {
function handleClick(e) {
console.log('Button clicked!');
console.log('Mouse X position:', e.clientX);
}
return <button onClick={handleClick}>Log Click</button>;
}#Handling Input Changes with onChange
The onChange event fires every time the value inside an input changes — on every single keystroke. This is how React keeps your UI in sync with what the user is typing. Paired with useState, it forms the foundation of controlled inputs (more on those in the next lesson). Here's a sneak peek:
import { useState } from 'react';
function NameInput() {
const [name, setName] = useState('');
function handleChange(e) {
setName(e.target.value);
}
return (
<div>
<input value={name} onChange={handleChange} placeholder="Type your name" />
<p>Hello, {name || 'stranger'}!</p>
</div>
);
}#Passing Extra Arguments to Handlers
Sometimes you need to pass extra data to your handler — for example, when you have a list of items and each delete button needs to know which item to delete. The trick is to wrap your handler in an arrow function inside the JSX:
function TodoList() {
const items = ['Buy milk', 'Walk the dog', 'Learn React'];
function handleDelete(item) {
console.log('Deleting:', item);
}
return (
<ul>
{items.map((item) => (
<li key={item}>
{item}
<button onClick={() => handleDelete(item)}>Delete</button>
</li>
))}
</ul>
);
}Inline Arrow Functions Are Fine Here
You might wonder: doesn't creating a new arrow function on every render waste memory? For most cases — lists of a few dozen items — the answer is no, the cost is negligible. Optimize only when you have measured a real performance problem, not in advance.
Which of the following correctly wires up a click handler in React?
You now have all the core tools for interactive React: onClick for buttons, onChange for inputs, the event object for context, and the rule that you always pass functions rather than calling them. These patterns appear in virtually every React component you'll ever write — you're already thinking like a React developer.
Key takeaways
- Use camelCase event props like onClick and onChange — not the lowercase HTML versions.
- Always pass the function reference (onClick={fn}), never call it (onClick={fn()}) — stray parentheses are a silent, common bug.
- React passes an event object to your handler; use e.target.value to read what's inside an input.
- To pass extra arguments, wrap your handler in an arrow function: onClick={() => handleDelete(item)}.
- onChange fires on every keystroke — that's what makes live, responsive inputs possible.
This code has a bug — what's wrong?
function HelloButton() {
function handleClick() {
alert('Hello from React!');
}
return <button onClick={handleClick()}>Say Hello</button>;
}This handler logs a message. When does the console.log run, given how onClick is wired up here?
function Logger() {
function report() {
console.log('clicked!');
}
return <button onClick={report}>Go</button>;
}This controlled input keeps state in sync as the user types. Inside handleChange, fill in the property that holds the input's current text so it can be passed to setName.
function NameInput() { const [name, setName] = useState(''); function handleChange(e) { setName(e.target.); } return <input value={name} onChange={handleChange} />; }
Arrange these lines into a TodoList that maps over items and gives each Delete button an onClick that passes the correct item to handleDelete.
<button key={item} onClick={() => handleDelete(item)}>Delete</button>}
function TodoList({ items }) {));
function handleDelete(item) { console.log('Deleting:', item); }return items.map((item) => (
Build a small 'Like' button component. It should display a heart button and a count that starts at 0. Each click on the button should increase the count by 1. As a bonus, change the button label to show the current count: e.g. '♥ 3 Likes'.
Try it live — edit the code and hit Run to see it rendered: