What does React's onClickCapture prop do that onClick does not, and when would you reach for it?
answer
- React exposes both DOM phases as props
- outermost first, then down to the target
- runs before a child can stop anything
- append Capture to any event prop
basics
~20 sonClickCapture registers the handler for the capture phase, so it runs on ancestors first, top-down, before the clicked element's own onClick. React offers a Capture variant of every event prop for intercepting an interaction before descendants handle it.
solid answer
~40 sReact exposes both phases of the DOM event model as props: `onClick` runs in the bubbling phase (target first, then upward through ancestors), while `onClickCapture` runs in the capture phase (outermost ancestor first, downward to the target). Every event prop has a `Capture` twin — `onKeyDownCapture`, `onFocusCapture`, `onPointerDownCapture` and so on. The practical reason to use it is **interception**: a capture handler on an ancestor sees the event before any descendant handler, so it still fires even if a child calls `e.stopPropagation()` — that call can only stop the remainder of the dispatch, not the part already done. Typical uses are analytics that must record every click, an overlay that swallows interaction during a drag, and closing a menu before an inner control reacts.
code
javascript · 10 linesfunction Toolbar() {
return (
<div
onClickCapture={() => console.log('1: ancestor capture')}
onClick={() => console.log('3: ancestor bubble')}
>
<button onClick={() => console.log('2: target')}>Run</button>
</div>
);
}go deeper
Know that onClick runs on the way up and onClickCapture on the way down, and that every event prop has a Capture twin.
Give the firing order for a nested example and explain the practical payoff: a capture handler on an ancestor runs before a descendant can call stopPropagation.
Show judgment about when interception is legitimate — analytics that must not be defeated, an overlay swallowing input during a drag — and when reordering phases is really hiding a structural problem.
Own the maintainability argument: capture handlers act at a distance and are invisible where the interaction is written, so treat them as a deliberate cross-cutting mechanism with an owner, not as a routine tool for local ordering fixes.
## Two phases, two props The DOM dispatches an event in three stages: capture, travelling from the root down to the target; the target itself; then bubbling, travelling back up. React surfaces both directions as props on your JSX: ```js <div onClickCapture={onOuterCapture} onClick={onOuterBubble}> <button onClick={onButton}>Run</button> </div> ``` Clicking the button logs, in order: `onOuterCapture`, `onButton`, `onOuterBubble`. The capture handler on the ancestor comes first because capture goes outside-in; the bubble handler on the same ancestor comes last because bubbling goes inside-out. The naming is entirely mechanical: append `Capture` to any event prop. `onClickCapture`, `onKeyDownCapture`, `onFocusCapture`, `onPointerDownCapture`, `onChangeCapture`, and so on for every event React supports. ## What capture is actually for Bubbling is the right default for almost everything, because handlers usually want to react to what happened beneath them. Capture answers a different need: **acting before descendants can**. The sharpest consequence is stopPropagation. A descendant that calls `e.stopPropagation()` in its bubble handler prevents the event from continuing upward — so an ancestor's `onClick` never runs. But an ancestor's `onClickCapture` already ran before the descendant was reached. Interception at capture is therefore robust against uncooperative children: ```js function Analytics({ children }) { return ( <div onClickCapture={(e) => track(e.target)}> {children} </div> ); } ``` This is the classic use: click tracking that must not be defeated by a third-party widget or a modal that calls stopPropagation internally. A second use is **swallowing**. During a drag, or while a blocking overlay is up, an ancestor can capture pointer or click events and call `e.stopPropagation()` there, so the interaction never reaches the interactive controls underneath: ```js <div onClickCapture={(e) => { if (isDragging) e.stopPropagation(); }}> ``` Stopping propagation *in the capture phase* prevents the event from ever reaching the target's handler at all — a strictly stronger action than doing it while bubbling. A third use is ordering: closing a popover or committing an inline edit before an inner control's own handler reacts to the same click. ## What it does not change Capture is about *when your React handler runs relative to other React handlers*, not about what the event is. The same SyntheticEvent object flows through both phases; `e.eventPhase` reports which stage you are in, `e.target` is the same node throughout, and `e.currentTarget` is whichever element's handler is running right now. `preventDefault()` works from either phase and, being about the browser's default action rather than the traversal, is unaffected by which phase you call it in. Also note that some React events do not bubble at all — `onMouseEnter` and `onMouseLeave` fire only on the element you attached them to. For events with no bubbling, the capture distinction has little practical use. ## Why it is easy to get wrong Two mistakes recur. The first is reaching for capture as a general fix for ordering problems that are really structure problems. If two handlers in your own tree fight over the same click, the maintainable fix is usually to move the logic to one owner, not to shuffle phases — capture handlers are invisible at the call site and surprising to the next reader. The second is assuming `stopPropagation()` in a capture handler cancels the interaction entirely. It cancels the rest of the React dispatch for that event. It does not cancel the browser's default action — that still needs `preventDefault()` — and it does not undo anything that already ran. ## Interview framing Name both phases, give the ordering for a concrete nested example, and then justify capture with the stopPropagation argument: it is the only place you can act *before* a descendant gets the chance to stop you. That is the answer an interviewer is listening for.
- If a child calls e.stopPropagation() in its onClick, which ancestor handlers still run?The ancestors' capture handlers — they already ran on the way down, before the child was reached. Their bubble handlers do not, because stopPropagation halts the remaining traversal. That asymmetry is the main reason to put must-not-miss logic such as analytics or an interaction shield in a capture handler.
- Does calling preventDefault in a capture handler behave differently from calling it while bubbling?No. preventDefault cancels the browser's default action for the event, and that is independent of where you are in the traversal — cancelling early or late gives the same result, since the default runs after dispatch completes. Only propagation is phase-sensitive. Later handlers can observe the cancellation via e.isDefaultPrevented().
- Do all React event props have a Capture variant?Every event that traverses the tree does — the naming is mechanical, so onKeyDown pairs with onKeyDownCapture, onFocus with onFocusCapture, and so on. The variant is meaningless for events that do not traverse: onMouseEnter and onMouseLeave fire only on the element they are attached to, so there is no descent to intercept.
saying these in an interview costs you the question
- Thinks onClickCapture means capturing the click's data
- Believes capture handlers run after the target's handler
- Says stopPropagation in a child also blocks capture handlers
- Assumes preventDefault only works in the bubble phase
- Uses capture to fix ordering that structure should fix