A page that cannot react to the user is a poster. React lets you attach event handlers to elements: functions that run when the user clicks, types, submits a form or moves the mouse. The syntax is close to HTML, with a few important differences. Handlers usually change state to update the screen, which is the next lesson; here we concentrate on how to attach and write them, and let the Console panel under each result show you what ran.
Adding a handler
Define a function inside your component and pass it to an element through an event prop such as onClick:
function App() {
function handleClick() {
console.log("The button was clicked");
}
return <button onClick={handleClick}>Click me</button>;
}
export default App;Click me
CONSOLE
The button was clickedThe log line appears in the Console area each time you press the button. Notes:
- Event prop names are camelCase:
onClick,onChange,onSubmit,onKeyDown,onMouseEnter. Notonclick. - The handler is usually a function declared inside the component, named
handleplus the event:handleClick,handleSubmit. Being inside the component lets it read the component's props and state. - A handler runs only when the event happens, never while the component renders. This is why event handlers are the right place for side effects.
You can also write the function inline when it is short:
function App() {
return <button onClick={() => console.log("inline arrow")}>Click</button>;
}
export default App;Click
CONSOLE
inline arrowPass the function, do not call it
The most common beginner bug is calling the function while writing the JSX:
function App() {
function handleClick() {
console.log("handleClick ran");
}
// Wrong: handleClick() runs while rendering. onClick receives its return value (undefined).
return <button onClick={handleClick()}>Click me</button>;
}
export default App;Click me
CONSOLE
handleClick ran
handleClick ranThe message appeared before anyone pressed the button, and clicking does nothing (twice in a row here, as StrictMode renders the component twice in development). With brackets you write the result of calling the function. Without brackets, onClick={handleClick}, you pass the function and React calls it later. The same goes for the inline form: write onClick={() => handleClick()}, an arrow function that calls it when the click happens.
Passing arguments to a handler
React calls your handler with one argument, the event. If you want to pass your own data, such as which item was clicked, wrap the call in an arrow function:
function App() {
const fruits = ["Apple", "Banana", "Cherry"];
function handleSelect(name) {
console.log("You chose " + name);
}
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>
{fruit} <button onClick={() => handleSelect(fruit)}>Choose</button>
</li>
))}
</ul>
);
}
export default App;Apple Choose
Banana Choose
Cherry Choose
CONSOLE
You chose BananaEach button gets its own arrow function that remembers its fruit. In the test I pressed the second button.
The event object
React passes your handler an event object that describes what happened. It is a wrapper around the browser's event, with the same useful properties, and works the same in every browser.
function App() {
function handleChange(event) {
console.log("typed:", event.target.value);
}
function handleClick(event) {
console.log("tag:", event.target.tagName, "type:", event.type);
}
return (
<div>
<input onChange={handleChange} placeholder="Type here" />
<button onClick={handleClick}>Inspect</button>
</div>
);
}
export default App;Inspect
CONSOLE
typed: h
typed: hi
tag: BUTTON type: clickThe properties you will use most:
| Property or method | Meaning |
|---|---|
event.target |
the element where the event started (the input, the button) |
event.target.value |
the current text of an input |
event.target.checked |
whether a checkbox is ticked |
event.currentTarget |
the element the handler is attached to |
event.type |
the kind of event, such as "click" |
event.key |
for keyboard events, the key pressed ("Enter") |
event.preventDefault() |
cancels the browser's default action |
event.stopPropagation() |
stops the event reaching parent handlers |
React's onChange fires on every keystroke in a text input, like the browser's input event, not only when the field loses focus.
Preventing default behaviour
Some elements have a built-in action: a link navigates, and a form submit reloads the page. In a React app you usually take over, so cancel the default with preventDefault():
function App() {
function handleSubmit(event) {
event.preventDefault(); // do not reload the page
console.log("form submitted, page not reloaded");
}
return (
<form onSubmit={handleSubmit}>
<input name="email" placeholder="Email" />
<button type="submit">Subscribe</button>
</form>
);
}
export default App;Subscribe
CONSOLE
form submitted, page not reloadedWithout preventDefault(), submitting would reload the page and everything on it would be lost. Put onSubmit on the <form>, not on the button, so that pressing Enter in the field also works. A form is covered in more depth in the Forms lessons.
Events bubble
When you click a button inside a div, the click happens on the button first and then bubbles up through its parent elements. Every ancestor with an onClick also runs, from the inside out:
function App() {
return (
<div onClick={() => console.log("div handler")} style={{ padding: 16, background: "#eee" }}>
<button onClick={() => console.log("button handler")}>Click</button>
</div>
);
}
export default App;Click
CONSOLE
button handler
div handlerBoth handlers ran, button first. That is usually what you want, and it is what lets one handler on a list watch all its items. When you do not want the parent to hear about it, call event.stopPropagation() in the inner handler:
function App() {
return (
<div onClick={() => console.log("div handler")} style={{ padding: 16, background: "#eee" }}>
<button onClick={event => { event.stopPropagation(); console.log("button handler only"); }}>
Click
</button>
</div>
);
}
export default App;Click
CONSOLE
button handler onlyUse stopPropagation sparingly; it can make behaviour surprising for other code that expects to hear about the click. If you need to react before children, React also supports capture handlers such as onClickCapture.
Handlers as props
A child component often needs to tell its parent that something happened. The parent passes a function down as a prop, and the child calls it from its own handler. By convention, the prop name starts with on and the function in the parent starts with handle:
function DeleteButton({ onDelete, label }) {
return <button onClick={onDelete}>{label}</button>;
}
function App() {
function handleDelete() {
console.log("Parent: delete requested");
}
return <DeleteButton label="Delete item" onDelete={handleDelete} />;
}
export default App;Delete item
CONSOLE
Parent: delete requestedThis is how information flows up in React, a design you will use constantly: data goes down in props, and requests go up as function calls.
Other common events
| Event prop | Fires when |
|---|---|
onClick, onDoubleClick |
the element is clicked |
onChange |
an input's value changes (every keystroke) |
onSubmit |
a form is submitted |
onKeyDown, onKeyUp |
a key is pressed or released |
onFocus, onBlur |
the element gains or loses focus |
onMouseEnter, onMouseLeave |
the pointer enters or leaves the element |
onScroll |
an element scrolls |
A keyboard example: pressing Enter in a field runs an action.
function App() {
function handleKeyDown(event) {
if (event.key === "Enter") {
console.log("Enter pressed");
}
}
return <input onKeyDown={handleKeyDown} placeholder="Press Enter" />;
}
export default App;CONSOLE
Enter pressedCommon mistakes
- Calling the handler:
onClick={handleClick()}instead ofonClick={handleClick}. - Using the HTML spelling
onclick, or a string:onClick="doSomething()". - Forgetting
event.preventDefault()in a form handler, then wondering why the page reloads. - Putting
onSubmiton the button instead of the form. - Putting side effects (logging, fetching) directly in the component body instead of in a handler.
- Writing
onClick={handle(id)}to pass an argument; wrap it:onClick={() => handle(id)}. - Overusing
stopPropagation, which hides events from code that needs them.
Exercise
- Add a button that logs the current time (
new Date().toLocaleTimeString()) when clicked. - Make a text input that logs its value on every keystroke, then change it to log only when Enter is pressed.
- Write a list of three links. Use
preventDefault()in the click handler and log which link was clicked instead of navigating. - Put a button inside a div, give both handlers, and predict the order of the logs before you run it.
Key points
- Attach handlers with props such as onClick, onChange and onSubmit, and pass a function: onClick={handleClick}, not onClick={handleClick()}.
- Handlers are usually defined inside the component, named handleSomething, and passed to children as onSomething props.
- The handler receives an event object: use event.target, event.preventDefault() and event.stopPropagation().
- Events bubble from the clicked element up through its parents unless you stop them; handlers do not run during rendering.