How to Use the React onClick Property on Buttons: A Complete Guide
To use the React onClick property on a button, pass a function reference to the onClick prop in JSX (e.g., <button onClick={handleClick}>), which React converts into a cross-browser SyntheticEvent listener via the internal SimpleEventPlugin.
The onClick property in React provides a declarative way to handle user interactions on button elements. According to the facebook/react source code, React normalizes browser differences through its SyntheticEvent system, ensuring consistent behavior across all supported browsers. Understanding how React processes this prop—from registration in the reconciler to event pooling—helps you write performant, bug-free event handlers.
How React onClick Works Under the Hood
When you add an onClick prop to a JSX button element, React does not simply attach a native DOM listener. Instead, the framework routes the prop through its internal event system for performance optimization and cross-browser compatibility.
Event Registration via SimpleEventPlugin
React registers click listeners during the initial render using the SimpleEventPlugin located in packages/react-dom/src/events/plugins/SimpleEventPlugin.js. This plugin maps the onClick prop to the native click event and manages the capture and bubbling phases. The reconciler delegates event listening to the root container rather than individual button elements, reducing memory overhead in large applications.
SyntheticEvent Pooling and Reuse
For each click interaction, React creates a SyntheticMouseEvent—a subclass of SyntheticEvent defined in packages/react-dom/src/events/SyntheticEvent.js. This wrapper normalizes properties like event.target and event.currentTarget across browsers. To minimize garbage collection, React pools these event objects and reuses them across renders. Consequently, all properties on the synthetic event become null after the event handler finishes executing.
React onClick Syntax and Code Examples
The following patterns demonstrate how to implement the onClick property correctly in both functional and class components.
Basic Functional Component Usage
In functional components, define the handler as a constant function and pass it directly to the onClick prop:
import React, { useState } from 'react';
export default function CounterButton() {
const [count, setCount] = useState(0);
const handleClick = (event) => {
// event is a SyntheticMouseEvent
event.preventDefault();
setCount(prev => prev + 1);
};
return (
<button onClick={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
);
}
The event object provides standard methods like stopPropagation() and preventDefault(), behaving identically across Chrome, Safari, and Firefox.
Class Component Method Binding
In class components, you must bind the handler to the instance to preserve the this context:
import React from 'react';
class ToggleButton extends React.Component {
constructor(props) {
super(props);
this.state = { on: false };
// Required binding for class methods
this.toggle = this.toggle.bind(this);
}
toggle(event) {
this.setState(prev => ({ on: !prev.on }));
}
render() {
return (
<button onClick={this.toggle}>
{this.state.on ? 'ON' : 'OFF'}
</button>
);
}
}
Alternatively, use a class field arrow function (toggle = () => { ... }) to avoid constructor binding.
Passing Parameters to Event Handlers
When you need to pass additional data alongside the event, wrap the handler in an arrow function:
function Item({ id, onSelect }) {
return (
<button onClick={() => onSelect(id)}>
Select Item #{id}
</button>
);
}
Performance note: Inline arrow functions create new function instances on every render. For large lists or frequently re-rendering components, memoize the callback using useCallback to prevent unnecessary child re-renders.
Asynchronous Handlers and Event Persistence
If you access the event after an await statement, you must call event.persist() to remove the object from the pool:
function SaveButton() {
const handleSave = async (event) => {
event.persist(); // Opt-out of pooling
const response = await fetch('/api/save', { method: 'POST' });
console.log('Target after await:', event.currentTarget);
};
return <button onClick={handleSave}>Save</button>;
}
Without persist(), React clears the event properties before the asynchronous code executes, resulting in null values.
Common Pitfalls When Using React onClick
Avoid these frequent mistakes when implementing button click handlers:
- Accessing pooled events asynchronously: Extract values like
event.target.valueinto local constants beforeawaitcalls, or useevent.persist(). - Unbound class methods: Always bind class methods in the constructor or use arrow function class fields to prevent
thisfrom beingundefined. - Overusing inline arrow functions: In large datasets, inline handlers (
onClick={() => ...}) hurt performance by forcing re-renders. UseuseCallbackinstead. - Relying on
event.nativeEvent: While accessible, the native event bypasses React's normalization. Prefer the synthetic API unless you need browser-specific behavior.
Summary
- React's
onClickprop attaches to buttons via the SimpleEventPlugin inpackages/react-dom/src/events/plugins/SimpleEventPlugin.js. - Event handlers receive a SyntheticMouseEvent that normalizes cross-browser behavior.
- Events are pooled for performance; use
event.persist()when accessing them asynchronously. - Class components require method binding or arrow functions to maintain
thiscontext. - Memoize handlers with
useCallbackwhen passing them to optimized child components.
Frequently Asked Questions
Why is my React onClick handler not working in a class component?
In class components, JavaScript does not automatically bind methods to the instance. If you pass this.handleClick directly to onClick, this will be undefined inside the method. Bind the method in the constructor (this.handleClick = this.handleClick.bind(this)) or define it as an arrow function class field.
How do I stop a button from submitting a form in React?
Call event.preventDefault() inside your onClick handler. This prevents the default browser behavior (form submission and page reload) while allowing React to process the event through its SyntheticEvent system.
Can I use async/await inside a React onClick handler?
Yes, but you must handle SyntheticEvent pooling. Either call event.persist() at the start of your async function, or extract all necessary values from the event object before the first await statement. Otherwise, React clears the event properties before your async code runs.
What is the difference between onClick and onClickCapture in React?
onClick listens during the bubbling phase, while onClickCapture listens during the capture phase (before the event reaches the target). React implements both through the same SimpleEventPlugin system, allowing you to choose when your handler executes in the event propagation lifecycle.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s https://instagit.com/install.md