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.value into local constants before await calls, or use event.persist().
  • Unbound class methods: Always bind class methods in the constructor or use arrow function class fields to prevent this from being undefined.
  • Overusing inline arrow functions: In large datasets, inline handlers (onClick={() => ...}) hurt performance by forcing re-renders. Use useCallback instead.
  • 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 onClick prop attaches to buttons via the SimpleEventPlugin in packages/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 this context.
  • Memoize handlers with useCallback when 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:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client