Skip to main content

onClick Event Handler: Pass vs Call in React

The onClick event is the foundation of interactive React applications. This guide covers how to properly attach click handlers to buttons and elements, the critical difference between passing and calling functions, and common mistakes that trip up beginners.

Key Takeaways

  • Pass function references to onClick, never call them: onClick={handleClick} not onClick={handleClick()}
  • Calling the function handleClick() executes it immediately during render, not on click
  • Define event handlers inside components so they access props and state
  • Use semantic <button> elements for accessibility (keyboard focus, screen readers)
  • Inline arrow functions onClick={() => {}} work well for simple, one-off handlers
  • Follow the handleEventName naming convention for clarity

Handling Clicks: Three Steps

Responding to a click in React requires three steps: define a handler function, choose which element receives the onClick prop, and pass the function reference (not call it).

function AlertButton() {
// Step 1: Define the event handler
function handleClick() {
alert('Button clicked!');
}

// Step 2 & 3: Attach handler to button
return <button onClick={handleClick}>Click Me</button>;
}

Breaking this down: function handleClick() { } defines a regular JavaScript function that runs when clicked. onClick={handleClick} passes the function reference to React, which calls it later. The function is not called immediately—React stores it and triggers it on user click.

The Critical Mistake: Passing vs. Calling

The most common beginner error is accidentally calling the function during render instead of passing its reference. This single character makes a massive difference:

Correct — passing the function reference:

<button onClick={handleClick}>

React receives the function and calls it when the user clicks.

Incorrect — calling the function immediately:

<button onClick={handleClick()}>

The parentheses mean the function executes now, during render, not later on click. The alert fires immediately when the page loads, and onClick receives undefined (the return value). The button does nothing when clicked because you've already executed the function and assigned its result to onClick.

This mistake causes alerts on page load, state updates at the wrong time, and confusing behavior. Always remember: handleClick (reference) not handleClick() (call).

Inline Arrow Functions for Simple Handlers

For short, one-off logic, define the handler directly in JSX using an arrow function:

function InlineButton() {
return (
<button onClick={() => alert('Inline handler!')}>
Click Me
</button>
);
}

The syntax onClick={() => alert(...)} defines an anonymous arrow function and passes it to React. This is still passing a function reference—the arrow function () => { } is the reference. React stores it and calls it on click.

Inline handlers are convenient for simple logic (single-line operations). For complex handlers or handlers used by multiple elements, define a separate named function for readability.

Defining Event Handlers Inside Components

Event handlers are always defined inside the component, not outside. This gives them access to the component's props and state:

function Counter() {
const [count, setCount] = React.useState(0);

// Handler has access to count and setCount via closure
function handleIncrement() {
setCount(count + 1);
}

return (
<div>
<p>Count: {count}</p>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}

If handleIncrement were defined outside the component, it would not have access to count or setCount. Defining it inside creates a closure that captures these values.

Using Semantic HTML for Accessibility

Always use a <button> element for clickable actions, not <div> or <span>. Buttons come with built-in accessibility:

  • Keyboard focus: Users can tab to buttons with the keyboard
  • Screen reader support: Assistive devices recognize buttons as interactive
  • Enter/Space key: Buttons respond to keyboard activation, not just clicks
  • Styling: Browsers apply default button styles (can be overridden)
// Good - semantic, accessible
<button onClick={handleClick}>Submit</button>

// Bad - loses keyboard and screen reader support
<div onClick={handleClick}>Submit</div>

If you must use a non-button element, add role="button", tabIndex="0", and keyboard handlers to match button behavior.

Handler Naming Convention

Follow the handle + EventName pattern: handleClick, handleChange, handleSubmit, handleMouseEnter. This naming convention signals to other developers that the function is an event handler, improving code readability.

function Form() {
const handleInputChange = (event) => {
console.log(event.target.value);
};

const handleFormSubmit = (event) => {
event.preventDefault();
};

return (
<form onSubmit={handleFormSubmit}>
<input onChange={handleInputChange} />
<button type="submit">Submit</button>
</form>
);
}

Frequently Asked Questions

Why can't I just call the function with parentheses?

Calling the function immediately onClick={handleClick()} executes it during render, not on click. You end up assigning the return value (usually undefined) to onClick instead of the function itself. React has nothing to call when the user clicks.

Can I pass arguments to an onClick handler?

Yes, use an arrow function wrapper:

<button onClick={() => handleDelete(item.id)}>
Delete
</button>

The arrow function captures item.id and passes it to handleDelete when clicked. Alternatively, use .bind() (less common in modern React):

<button onClick={handleDelete.bind(null, item.id)}>
Delete
</button>

What is SyntheticEvent and why should I care?

React wraps browser events in its own event system called SyntheticEvent. It normalizes events across browsers and provides pooling for performance. In most cases, you don't need to think about it—use event.target, event.preventDefault() as usual. The main gotcha: if you need the event asynchronously, call event.persist() to prevent pooling.

Should I use inline functions or separate named functions?

Use separate named functions for complex logic (improves readability), reusable logic (used by multiple elements), or when debugging (better stack traces). Use inline arrows for simple, one-off handlers. Example of appropriate separation:

// Simple, one-off → inline is fine
<button onClick={() => setCount(count + 1)}>+</button>

// Complex or reused → separate function
function handleComplexSubmit(event) {
event.preventDefault();
validateForm();
if (isValid) submitToAPI();
}
<form onSubmit={handleComplexSubmit}>

Why does my handler fire immediately on page load?

You're calling the function instead of passing it. Change onClick={handleClick()} to onClick={handleClick}.

Further Reading