Handling Events
BeginnerPass a function, do not call one. Most early event bugs are one pair of parentheses in the wrong place.
Overview
React event props take a function reference that React will call later. Writing onClick={handleClick()} calls it during render instead — which usually triggers a state update, which triggers a render, which calls it again. Beyond that, React events are close enough to DOM events that your browser knowledge transfers, with two differences worth knowing: the naming is camelCase, and React attaches a single listener at the root rather than one per element.
Passing Handlers
Reference, not call. Arrow wrappers are how you pass arguments.
<button onClick={handleRun}>Run</button> // correct — a reference
<button onClick={handleRun()}>Run</button> // WRONG — calls it now
// Passing an argument needs a wrapper
<button onClick={() => onOpen(problem.slug)}>Open</button>
// Inline is fine. "It creates a new function every render" is true
// and almost never the reason a page is slow — measure before
// contorting the code around it.
// The event object
function handleSubmit(e) {
e.preventDefault() // stop the browser navigating
e.stopPropagation() // stop it reaching parent handlers
e.currentTarget.value
}
// Common handlers
onChange onInput onSubmit onKeyDown onFocus onBlur
onMouseEnter onPointerDown onScrollHow React Delivers Events
One listener at the root, and a normalised event object.
// React does NOT attach a listener to each element. It attaches one
// per event type at the root container and dispatches from there.
// So handlers work for elements added later, and adding a thousand
// rows does not add a thousand listeners.
// The event you receive is a SyntheticEvent — a cross-browser
// wrapper. The real DOM event is on e.nativeEvent when you need it.
// Because React listens at the root, a document-level listener you
// add yourself runs BEFORE React's handlers, which matters for
// "click outside to close" logic:
useEffect(() => {
const onDocClick = (e) => {
if (!ref.current?.contains(e.target)) setOpen(false)
}
document.addEventListener('mousedown', onDocClick)
return () => document.removeEventListener('mousedown', onDocClick)
}, [])
// mousedown, not click — click fires after the element may have
// already been removed from the DOM.Handlers and Keyboard Access
What an event handler owes a keyboard user, which is where most accessibility failures start.
// A div with onClick is invisible to a keyboard
<div onClick={select}>Select</div> // unreachable
<button onClick={select}>Select</button> // just works
// If a non-button truly must be interactive, it needs all of this
<div role="button" tabIndex={0} onClick={select}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && select()}>
// Async handlers — disable while in flight, or a double-click
// submits twice
async function handleSubmit(e) {
e.preventDefault()
setSubmitting(true)
try { await save(values) }
finally { setSubmitting(false) }
}
<button disabled={submitting}>{submitting ? 'Saving…' : 'Save'}</button>Key Points to Remember
- 1Event props take a function reference; adding parentheses calls it during render
- 2Wrap in an arrow function to pass arguments — inline handlers are rarely a real performance problem
- 3React attaches one listener per event type at the root and hands you a normalised SyntheticEvent
- 4A document listener you add yourself fires before React handlers — use mousedown for click-outside
- 5A clickable div is unreachable by keyboard; use a button, and disable it while a submit is in flight
Interview Questions
Sign in to ask AriaWhat happens if you write onClick={handleClick()} instead of onClick={handleClick}?
How does React attach event listeners, and why does that design help?
Why does click-outside logic usually listen for mousedown rather than click?
Ask Aria about Handling Events
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.