In React JSX, what is the difference between writing onClick={handleClick} and onClick={handleClick()}, and what actually happens with the second form?
answer
- reference versus call expression
- braces evaluate during render
- undefined prop, dead button
- side effect on every render
- factory returning a handler is fine
basics
~20 sThe first passes the function so React can call it on click. The second calls it during render and passes its return value, usually undefined, so nothing happens on click and the side effect fires on every render.
solid answer
~40 s`onClick={handleClick}` hands React a reference to the function; React stores it and calls it when the click event is dispatched. `onClick={handleClick()}` invokes the function while JSX is being evaluated — that is, during render — and passes whatever it returned as the prop. For a typical handler that returns nothing, the prop is `undefined`, so the button appears dead while the handler's side effect fires on every render. If the handler sets state, that render-time call triggers another render, which calls it again, and React aborts with "Too many re-renders". The exception is a factory that deliberately *returns* a handler, where `onClick={makeHandler(id)}` is correct. If you just need to pass arguments, wrap it: `onClick={() => handleClick(id)}`.
code
javascript · 17 linesimport { useState } from 'react';
function Counter() {
const [n, setN] = useState(0);
const inc = () => setN(n + 1);
const makeInc = step => () => setN(v => v + step);
return (
<>
<button onClick={inc}>reference: {n}</button>
<button onClick={() => setN(n + 1)}>wrapper</button>
<button onClick={makeInc(5)}>factory returns a handler</button>
</>
);
}
export default Counter;go deeper
Know that the braces hold an expression evaluated during render, and be able to say that the parenthesised form calls the function immediately and stores its return value as the prop.
Explain both consequences — a prop of undefined and a side effect running at render time — and connect the state-updating case to React's "Too many re-renders" guard.
Demonstrate that you catch this in review, know the legitimate factory case where calling is correct, and can weigh the arrow wrapper against a factory when a stable identity is needed.
Frame the guardrails: typed event props, lint coverage, and a review habit that treats a call expression in an event prop as suspicious, so the class of bug does not depend on individual vigilance.
## The prop wants a function, not a result A JSX attribute in braces is an ordinary JavaScript expression, evaluated at the moment the element is created — during render. React takes the resulting value and stores it as the element's prop. For an event prop such as `onClick`, React expects that value to be a function it can call later, when the user clicks. ```jsx <button onClick={handleClick}>Save</button> // reference: React calls it on click <button onClick={handleClick()}>Save</button> // call: the result becomes the prop ``` The two lines differ by two characters and by everything else. The first evaluates the identifier `handleClick` to a function object. The second evaluates a *call expression*: JavaScript runs `handleClick` right there and uses its return value. ## What the buggy form does Two symptoms follow, and a candidate should name both. **The button does nothing.** Most handlers return nothing, so the call evaluates to `undefined` and the element ends up with `onClick={undefined}` — the same as having no handler at all. Clicking produces silence, and there is no error to lead you to the cause. **The side effect fires at the wrong time.** Whatever the handler does — a network request, an analytics call, a state update — now happens while the component renders, and again on every subsequent render. Rendering is supposed to be a pure computation of what the UI should look like; performing effects there is exactly the thing React's model forbids. ## The infinite-render variant If the handler updates state, the two symptoms compound into a hard failure: ```jsx function Counter() { const [n, setN] = useState(0); // render -> setN -> re-render -> setN -> ... return <button onClick={setN(n + 1)}>{n}</button>; } ``` Render calls the setter, the state change schedules another render, that render calls the setter again. React detects the runaway loop and throws "Too many re-renders. React limits the number of renders to prevent an infinite loop." That message is a strong signal to look for a called-instead-of-passed handler, or for a state update sitting directly in the component body. ## When calling is correct The form is not always wrong. If the function is a *factory* whose return value is itself a function, calling it in the prop is exactly right: ```jsx const makeHandler = id => () => select(id); <button onClick={makeHandler(item.id)}>Pick</button> // prop is the inner function ``` Here the call happens during render, but it returns a handler, so the prop holds a function again. This is one of the two standard ways to pre-bind an argument. ## Passing arguments the usual way The reason people reach for the parenthesised form is almost always that they want to pass an argument. The idiomatic solutions are: - **An arrow wrapper:** `onClick={() => handleClick(id)}`. The prop is a function; the argument is captured by the closure. Costs one closure per render, which is normally irrelevant. - **A factory**, as above, when the same binding logic is reused. - **`Function.prototype.bind`:** `onClick={handleClick.bind(null, id)}`. Equivalent in effect and also allocates per render, but reads less clearly to most React developers than the arrow. All three keep a function in the prop. The distinction to internalise is: whatever the expression in the braces evaluates to is what React will try to call on the event. ## Spotting it TypeScript usually catches the mistake, because the event prop is typed to accept a function and you have supplied the call's result instead. In plain JavaScript there is no warning at all, so the tell is behavioural: a control that silently does nothing, an effect that runs on mount without you asking, or a "Too many re-renders" crash. When reviewing, read every event prop and ask whether the expression *is* a function or *produces* something else.
- What is the fastest way to tell the two apart when you are reviewing someone else's JSX?Ask what the expression inside the braces evaluates to. An identifier or an arrow evaluates to a function and is fine; a call expression evaluates to whatever the function returned, which is only fine when that return value is itself a function. Trailing parentheses in an event prop deserve a second look every time.
- Someone says onClick={() => handleClick(id)} is wasteful and wants onClick={handleClick(id)} instead. What do you tell them?That the second form does not do what they think: it calls `handleClick` during render and stores the result. The arrow wrapper is the correct way to pre-bind an argument, and its cost is a single closure allocation per render, which is negligible. If they want a stable identity for a memoized child, a factory or a callback that takes the id from the child is the answer, not removing the wrapper.
- React throws "Too many re-renders". What are the first two causes you check?A handler invoked in an event prop instead of passed (`onClick={setOpen(true)}`), and a state update written directly in the component body rather than inside a handler or an effect. Both make rendering schedule another render unconditionally, which is the loop React is protecting you from.
saying these in an interview costs you the question
- Thinks the parentheses make React call the handler on click
- Uses onClick={fn(arg)} as the way to pass arguments
- Cannot explain why the button silently does nothing
- Blames "Too many re-renders" on a hook rather than the invocation
- Says the arrow wrapper is a performance problem worth avoiding