Click Handlers and Event Props
A component render calculates what the interface should contain. An event handler runs later, after React DOM has committed an element and the user interacts with it.
function ContinueButton() {
function handleClick() {
console.log("Continue reading");
}
return <button onClick={handleClick}>Continue</button>;
}During rendering, JSX stores the handleClick function in the button's onClick prop. React DOM connects the event system to the browser. The function body does not run until a click activates the button.
component renders
-> button receives handler prop
-> React DOM commits button
-> user activates button
-> React calls handlerThis timing makes event handlers a supported place for work caused by a particular user action, including requesting a state update.
Pass the Function Without Calling It
This JSX passes a function value.
<button onClick={handleClick}>Continue</button>This JSX calls the function while rendering.
<button onClick={handleClick()}>Continue</button>Parentheses perform a JavaScript call immediately. The return value from handleClick() becomes the event prop. A normal handler returns undefined, so the element has no callable handler for the later click.
The visible symptom can include a log or navigation that occurs as soon as the component renders, followed by a button that does nothing when clicked.
Event props receive function values. Do not add call parentheses when the named handler needs no argument at render time.
An inline arrow also supplies a function value.
<button onClick={() => console.log("Continue")}>
Continue
</button>React calls the arrow after the event. The arrow body then calls console.log.
Use an Arrow to Supply an Argument
A list button needs the current lesson ID.
function LessonItem({ lesson, onOpen }) {
return (
<button onClick={() => onOpen(lesson.id)}>
{lesson.title}
</button>
);
}The arrow function captures lesson.id from this render. Clicking the button calls onOpen with that ID.
Do not call the callback directly in the prop.
<button onClick={onOpen(lesson.id)}>
{lesson.title}
</button>That call runs while LessonItem renders.
A named local handler can keep the JSX shorter.
function handleOpen() {
onOpen(lesson.id);
}return <button onClick={handleOpen}>{lesson.title}</button>;Choose the version that keeps the component's action visible. Optimize callback references only after profiling demonstrates a need.
Name Events by Intent Across Components
Host event props use names such as onClick, onChange, onSubmit, and onKeyDown.
Custom component props can name the application intent.
<LessonItem
lesson={lesson}
onOpenLesson={handleOpenLesson}
/>The child connects that intent to its current host interaction.
function LessonItem({ lesson, onOpenLesson }) {
return (
<button onClick={() => onOpenLesson(lesson.id)}>
{lesson.title}
</button>
);
}The parent decides what opening a lesson does. The child renders the element that requests the action.
Use on names for callback props and handle names for functions that implement them.
function handleOpenLesson(lessonId) {
console.log("open", lessonId);
}The convention distinguishes a supplied event opportunity from the local function that handles it.
Custom callback prop names do not register browser events by themselves. The receiving component must use or forward the function.
React Passes an Event Object
React calls a handler with an event object.
function handleClick(event) {
console.log(event.type);
}For a click, event.type is click. The event also exposes methods and properties that follow the browser event interface, including preventDefault, stopPropagation, target, and currentTarget.
React event objects also expose nativeEvent when the underlying browser event is required. Most component code should use the React event interface so it remains aligned with React's event system.
Modern React events are not pooled. Old code can contain event.persist() from earlier React versions. It is no longer required to retain event properties after the handler returns.
Distinguish target and currentTarget
target identifies the deepest event target. currentTarget identifies the element whose handler is currently running.
function handleClick(event) {
console.log(event.target);
console.log(event.currentTarget);
}return (
<button onClick={handleClick}>
<span>Continue</span>
</button>
);Clicking the text can make the span the target. The button is the current target while the button handler runs.
Use currentTarget when reading a property from the element where the handler was attached.
function handleClick(event) {
event.currentTarget.focus();
}Do not assume target has the same element type as currentTarget when nested content exists.
Events Propagate Through Host Ancestors
Most browser events propagate from the target through ancestor elements.
function LessonCard() {
function handleCardClick() {
console.log("card");
}
return (
<article onClick={handleCardClick}>
<button type="button">Bookmark</button>
</article>
);
}Clicking the button produces a click that can reach the article handler even though the button has no handler of its own.
Add a button handler and both can run.
<button type="button" onClick={handleBookmarkClick}>
Bookmark
</button>The button handler runs during the bubbling path before the event reaches the article handler.
Stop propagation only when the nested interaction must not activate the ancestor behavior.
function handleBookmarkClick(event) {
event.stopPropagation();
saveBookmark();
}The event no longer continues to later ancestors in that propagation path.
Do not stop propagation in every handler. Ancestor features can rely on events for dismissal, logging, or coordinated interaction.
Capture Handlers Run on the Way Down
Add Capture to a React event prop to handle the capture phase.
<section onClickCapture={handleCapturedClick}>
<button onClick={handleButtonClick}>Continue</button>
</section>The section capture handler runs before the target's normal click handler. Normal bubbling handlers then run from the target upward unless propagation is stopped.
Capture is useful for selected logging and coordination cases. The first app does not need it for ordinary button actions.
onScroll is a notable React event that does not propagate like most events. Attach it to the specific scrolling element whose scroll you need to observe.
Prevent a Browser Default Separately
Some events have a default browser action. A form submission can send data and load another document. A link activation follows its href.
Prevent the default action when the component provides a replacement behavior.
function handleSubmit(event) {
event.preventDefault();
console.log("save in client state");
}<form onSubmit={handleSubmit}>
<button type="submit">Save</button>
</form>preventDefault does not stop propagation. stopPropagation does not prevent a default action. They control different parts of the event.
preventDefault -> cancel browser default when cancelable
stopPropagation -> stop movement through ancestorsDo not prevent a link's navigation only to reproduce the same navigation through click code. Use a real anchor and allow the browser behavior unless the interaction genuinely requires interception.
Handle Submission on the Form
Put the submit handler on form.
<form onSubmit={handleSubmit}>
<input name="query" />
<button type="submit">Search</button>
</form>This handles clicking the submit button and keyboard submission from a form control.
Handling only the button click misses other valid submission paths.
<button type="submit" onClick={handleSubmit}>Search</button>The handler also receives a click event rather than the form submit event. Handle submission on the form's onSubmit prop.
Declare Button Type Inside Forms
A button inside a form defaults to submit behavior when no type is specified.
<form>
<button onClick={handleReset}>Reset filters</button>
</form>Clicking this reset control can also submit the form.
Declare an ordinary action button.
<button type="button" onClick={handleReset}>
Reset filters
</button>Use type="submit" for the control that submits the form.
The explicit type makes the browser behavior visible in source.
Use the Element with the Required Behavior
A div with a click handler does not gain button keyboard behavior.
<div onClick={handleContinue}>Continue</div>Use a button for an action.
<button type="button" onClick={handleContinue}>
Continue
</button>Use an anchor for navigation.
<a href="/learn/state-events-from-scratch">
Read the chapter
</a>The browser provides keyboard activation, focus behavior, form behavior, and accessibility semantics for the native element. A React event prop does not recreate all of them on a generic container.
Select the HTML element from the interaction. Add the React handler to that element.
Event Work Can Change External Systems
Rendering must remain pure because React controls when component calculations run. An event handler runs in response to a specific interaction, so it can perform an action caused by that interaction.
function handleCopy() {
navigator.clipboard.writeText(lessonUrl);
}function handlePrint() {
window.print();
}State setters, analytics events, and mutation requests can also begin in event handlers when the product action requires them.
The operation still needs error handling, permission handling, cancellation, and user feedback where relevant. Event timing only establishes the correct trigger.
Do not move work caused by a click into an Effect merely because it is asynchronous. The event handler already provides the correct trigger.
Handler Errors Occur After Rendering
This component can render successfully.
function ContinueButton() {
function handleClick() {
missingFunction();
}
return <button onClick={handleClick}>Continue</button>;
}The error appears only after the click. Component recovery mechanisms for descendant render failures do not automatically handle event-handler exceptions. Catch an expected operational failure in the handler or in the async operation it starts, then show supported feedback.
Do not wrap every handler in a silent catch.
try {
await saveGoal();
} catch (error) {
// ignored
}An ignored error leaves the user without a result and removes evidence needed for diagnosis.
The Handler Reads One Render's Values
Each render creates its own handler closures.
function LessonItem({ lesson }) {
function handleOpen() {
console.log(lesson.id);
}
return <button onClick={handleOpen}>{lesson.title}</button>;
}The handler created during this render closes over this render's lesson binding. When the parent later renders another lesson prop, React creates another handler for the next element output.
The same rule applies to state. A handler sees the state snapshot from the render that created it.
Build the First Event Path
Keep the action in the parent.
function App() {
function handleOpenLesson(lessonId) {
console.log(`Open ${lessonId}`);
}
return (
<LessonItem
lesson={{ id: "events", title: "Click Handlers" }}
onOpenLesson={handleOpenLesson}
/>
);
}The child translates the button click into the component callback.
function LessonItem({ lesson, onOpenLesson }) {
return (
<button onClick={() => onOpenLesson(lesson.id)}>
{lesson.title}
</button>
);
}Click and inspect the console. Then remove the temporary log; diagnostic code should not become permanent application behavior.
Check the Event Path
The interaction path now has a precise order.
render passes a function through an event prop
React DOM connects browser event handling
the user activates the semantic element
React calls the handler with an event object
the handler performs work caused by that interactionSource Notes
- React documentation, Responding to Events
- React DOM reference, Common components
- MDN, Event bubbling
- MDN,
Event.preventDefault()