I’m trying to understand the order in which events are being executed in a React application. Given the following sample component, why would I be observing "OUTER ELEMENT"
logged before "INNER ELEMENT"
when I click on the inner div
element?
I can see that the event is in the bubbling phase, so my understanding is that it should propagate from the target element upwards, but I am observing the opposite happen.
Is my understanding incorrect?
export const Test = () => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const currentRef = ref.current;
if (!currentRef) {
return;
}
const handler = (e) => {
console.log("OUTER ELEMENT");
};
currentRef.addEventListener("click", handler);
return () => {
currentRef.removeEventListener("click", handler);
};
});
return (
<div ref={ref}>
<div
onClick={(event) => {
console.log("INNER ELEMENT");
}}
>
INNER
</div>
OUTER
</div>
);
};
2
Answers
When a user clicks on an element, the event is first handled by that element’s onclick event listener. If the event is not explicitly stopped from propagating, it then "bubbles up" through the element’s parent elements, triggering their onclick event listeners in turn. This continues until the event reaches the outermost element or until an event handler calls the stopPropagation method.
In React, because it uses a virtual DOM to efficiently update the UI, it can sometimes batch and reorder event handlers to optimize performance. This can cause event listeners on outer elements to be executed before event listeners on inner elements, even if the inner element’s event listener was added first.
To ensure that event listeners are executed in a specific order, you can use the event.stopPropagation() method to prevent the event from bubbling up the DOM tree.
React uses a synthetic event system to fix some browser inconsistencies in event handling. (See also here.)
At the implementation level, it’s my understanding that React is actually attaching a single top-level handler for each event (to
document
or the React root or similar) and then dispatching that event through the React tree to the appropriateonFoo
handler.As a result, you can’t make assumptions about the order of execution between React events and native JS events, and using functions like
Event.stopPropagation
from React to influence JS may not work as expected. (Former React manager Sophie Alpert said, "We don’t guarantee anything about event ordering between React events and native events.")