A page that never reacts is just a poster. Events are how people talk to your app: they click, type, hover, press keys and submit forms. In React, handling events is simple and looks like HTML, with a few small differences. This lesson shows each of them.
Your first event handler
To react to a click, give the element an onClick prop. Its value is the function to run when the click happens.
function Hello() {
function handleClick() {
alert('You clicked the button!');
}
return <button onClick={handleClick}>Say hello</button>;
}
A good habit is to start the name of the handler with handle, such as handleClick or handleSubmit. It makes your code easy to read.
The most common mistake: calling the function
Look at the difference between these two lines:
<button onClick={handleClick}>Right</button> {/* passes the function */}
<button onClick={handleClick()}>Wrong</button> {/* runs it at once */}
The wrong version runs handleClick() while the page is being drawn, and passes its result to the button. Remember: pass the function, do not call it. Without brackets you give React the function to call later. With brackets you call it now.
Inline handlers
For very short actions, you can write the function directly. An arrow function is the usual way:
<button onClick={() => setCount(count + 1)}>Add</button>
If your handler is longer than one line, write a named function above, so the JSX stays easy to read.
Passing an argument
Often you need to tell the handler which item was clicked. Wrap the call in an arrow function:
function List({ items, onRemove }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<button onClick={() => onRemove(item.id)}>Remove</button>
</li>
))}
</ul>
);
}
The arrow function () => onRemove(item.id) waits until the click, then calls onRemove with the right id. Each button gets its own arrow function, so each remembers its own item.
The event object
React gives your handler one argument: the event object. It holds details about what happened. The most useful parts are:
event.target: the element that caused the event. For an input,event.target.valueis the text in it.event.preventDefault(): stops the browser's normal action, such as reloading the page when a form is submitted.event.key: for key events, the key that was pressed, such as'Enter'.event.stopPropagation(): stops the event from reaching parent elements.
function Search() {
const [text, setText] = useState('');
function handleChange(event) {
setText(event.target.value);
}
function handleKeyDown(event) {
if (event.key === 'Enter') {
console.log('Searching for', text);
}
}
return <input value={text} onChange={handleChange} onKeyDown={handleKeyDown} />;
}
Form submit
A form that is submitted makes the browser reload the page, which would throw away your state. Stop that with preventDefault:
function SignUp() {
const [email, setEmail] = useState('');
function handleSubmit(event) {
event.preventDefault();
console.log('Sending', email);
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Join</button>
</form>
);
}
Use onSubmit on the form, not onClick on the button. That way the form also works when the person presses Enter, and it works with screen readers and keyboards.
Common events
onClick: a click or tap.onChange: the value of an input changed. In React it fires on every key press.onSubmit: a form was submitted.onKeyDownandonKeyUp: a key was pressed or released.onMouseEnterandonMouseLeave: the pointer moved onto or off an element.onFocusandonBlur: an element got or lost focus.
Events travel up (bubbling)
When you click a button inside a box, the click first happens on the button, then bubbles up to the box and then to its parents. If both have an onClick, both run. If you do not want the parent to know, call event.stopPropagation() in the child.
<div onClick={() => console.log('box clicked')}>
<button onClick={(e) => { e.stopPropagation(); console.log('button clicked'); }}>
Click me
</button>
</div>
Child tells parent: functions as props
A child component cannot change the state of its parent directly. Instead the parent passes a function as a prop, and the child calls it when something happens. This is how data flows up in React.
function LikeButton({ onLike }) {
return <button onClick={onLike}>Like</button>;
}
function Post() {
const [likes, setLikes] = useState(0);
return (
<div>
<p>{likes} likes</p>
<LikeButton onLike={() => setLikes(likes + 1)} />
</div>
);
}
A common name for such props is onSomething, like onLike, onSave or onDelete. It tells the reader that this prop is an event that the parent can listen to.
Good habits for events
- Use real
buttonelements for clicks, not adiv. Buttons work with the keyboard and with screen readers for free. Addtype="button"to buttons inside forms that should not submit. - Keep handlers short. If a handler grows, move the work into a normal function.
- Do not forget
preventDefaultin form submit handlers. - Disable a button while a request is running, so people cannot click twice.
Common mistakes
- Writing
onclick(small letters). It must beonClick. - Calling the function:
onClick={save()}. - Forgetting
event.preventDefault()in a form, and then wondering why the page reloads. - Using a
divwithonClickfor something that is really a button. - Reading
eventinside a delayed function after it ended. Copy what you need first:const value = event.target.value.
A worked example: a tiny color picker
Let us put several events in one small component. It has buttons that choose a colour, a text box for a label, and a form that saves the choice.
function ColorPicker() {
const [color, setColor] = useState('tomato');
const [label, setLabel] = useState('');
const [saved, setSaved] = useState([]);
function handleSubmit(event) {
event.preventDefault();
if (!label.trim()) return;
setSaved([...saved, { id: crypto.randomUUID(), label: label.trim(), color }]);
setLabel('');
}
return (
<div>
{['tomato', 'seagreen', 'royalblue'].map((c) => (
<button key={c} type="button" onClick={() => setColor(c)} style={{ background: c, color: 'white' }}>
{c}
</button>
))}
<form onSubmit={handleSubmit}>
<input value={label} onChange={(e) => setLabel(e.target.value)} placeholder="Name this colour" />
<button type="submit">Save</button>
</form>
<ul>
{saved.map((s) => <li key={s.id} style={{ color: s.color }}>{s.label}</li>)}
</ul>
</div>
);
}
There are four kinds of events in this one small component: onClick on the colour buttons (with an argument), onChange on the input, onSubmit on the form, and the state updates that follow. Each event calls a function, the function changes state, and React draws the new screen. That loop is how every React app works. Notice also that the colour buttons have type="button", so they do not submit the form by mistake.
Questions beginners often ask
Why do I use an arrow function in onClick sometimes and not other times? If the handler needs no argument, pass the function name: onClick={handleSave}. If you need to give it an argument, such as an id, wrap the call: onClick={() => handleRemove(id)}. The wrapper waits until the click before it calls your function.
What is the difference between onChange and onInput? In React you almost always use onChange. It fires on every key press in a text input, which is what you want for keeping state in step with the field.
Can I use the same handler for many buttons? Yes. Pass different arguments in the arrow function, or read information from the event, such as event.currentTarget.dataset.id. One handler for many items keeps your code short.
Why does my page reload when I press a button in a form? A button inside a form submits it by default. Add type="button" to buttons that should not submit, and use event.preventDefault() in the submit handler.
Your task
- Make a button that toggles a message between “On” and “Off”.
- Make a text box that shows how many characters have been typed.
- Make a small form with a name field. On submit, add the name to a list on the page, and clear the input.
- Add a child component
DeleteButtonthat calls anonDeleteprop, and use it to remove names.
How to check it worked
- The page does not reload when you submit the form.
- Pressing Enter in the input submits the form.
- Deleting a name removes only that name.
Summary
- Events use camelCase props such as
onClick. Pass a function, do not call it. - Use an arrow function to pass arguments:
() => remove(id). - The event object gives
target.value,keyandpreventDefault(). - Child components tell the parent about events through function props.