React Event Propagation
React event propagation lets events move through capture and bubble handlers, with React-specific behavior when component ownership differs from DOM placement. The key distinction is between stopping propagation, canceling default browser behavior, and handling events that cross portal or root boundaries.
Browser event propagation normally has three stages: capture travels from the outer ancestors toward the target, the target is reached, and bubbling travels back outward. React exposes capture and bubble handlers through names such as `onClickCapture` and `onClick`. Most React handlers are bubble-phase handlers, so a click on a nested element can also invoke handlers on its ancestors.
A React event handler receives a normalized event object. Calling `event.stopPropagation()` stops that event from reaching further ancestor handlers in the propagation path; it does not undo handlers that already ran and does not generally cancel the browser's default action. Use `event.preventDefault()` when the default action itself must be canceled.
React's event system delegates many events rather than installing an independent listener on every element. In React 17 and later, the delegation boundary moved from `document` to the root container. This implementation detail matters when mixing React with native listeners or multiple React roots, so code should not assume that a React handler and a native listener have identical ordering.
Portals render into a different DOM location but remain connected to the same React ownership tree. Consequently, an event from a portal can bubble to a React ancestor in the component tree even when that ancestor is not a DOM ancestor. This is useful for modal and overlay components, but it means DOM inspection alone may not predict React handler propagation.
Use `onClickCapture` for deliberate interception or instrumentation and ordinary `onClick` for normal bubbling behavior. Prefer explicit data and component callbacks for business logic; relying on propagation across several layers can make components harder to reuse and test.
Key points
- Use `onClickCapture` for capture and `onClick` for bubbling.
- `stopPropagation()` limits propagation; `preventDefault()` cancels a default action.
- A child event can invoke ancestor React handlers during bubbling.
- Portal events can bubble through the React tree despite a different DOM location.
- React delegation and native listeners can interact differently across roots and React versions.
Interview questions
Describe event propagation and the difference between capture and bubbling in React.
Propagation has a capture phase, a target phase, and a bubble phase. Capture moves from ancestors toward the target, while bubbling moves from the target back toward ancestors. React exposes these phases with `onClickCapture` and `onClick`, respectively. A nested click can therefore trigger both the nested handler and ancestor bubble handlers unless propagation is stopped.
What is the difference between `stopPropagation()` and `preventDefault()`?
`event.stopPropagation()` prevents the event from continuing to ancestor handlers, but it does not cancel the browser's default behavior. For example, it can prevent a card's click handler from responding to a button click inside the card. `event.preventDefault()` instead cancels a cancelable default action, such as following a link. They can be used together when both effects are needed.
How does event propagation behave through a React portal?
A portal changes where React places a subtree in the DOM, but the subtree remains part of its original React tree. React event propagation follows that React relationship, so an event in a portal can reach an ancestor component even when that component is not a DOM ancestor of the portal container. This is why a modal rendered into `document.body` can still trigger a parent React handler. If that behavior is undesirable, stop propagation at an appropriate boundary or design the parent callback more explicitly.
How does React's event delegation affect propagation and native event listeners?
React delegates many events to a root-level listener instead of attaching a separate native listener to every element. In React 17 and newer, the delegation boundary is the root container rather than `document`; older versions used a different boundary. Mixing native and React listeners can therefore produce surprising ordering or propagation interactions, especially with multiple roots. The practical rule is to treat native-versus-React ordering as an implementation detail and test the exact integration when it matters.
When would you use `onClickCapture` instead of `onClick`?
A capture handler runs before the target's bubble handler, while a bubble handler runs after the target phase as the event travels outward. A parent `onClickCapture` can observe or intercept a descendant click before the descendant's ordinary `onClick` runs. Calling `stopPropagation()` during capture prevents the event from reaching later nodes in the propagation path. This makes capture useful for cross-cutting observation, but interception should be intentional because it can block component behavior.