Imagine you have the following code:
const [counter, setCounter] = useState(0);
return (
<div onClick={() => setCounter((prevCounter) => prevCounter + 1)}>
<button onClick={() => setCounter((prevCounter) => prevCounter + 1)}>
Counter value is: {counter}
</button>
</div>
);
That renders this button:
What would be displayed on that button if you click it?
If you guessed "Counter value is: 1", you were wrong!
What we get is this:
But why?
Understanding Event Propagation
In our example, although we clicked on the button
, the event handler of its div
parent was triggered as well. That's because events don't just affect the target element that generated the event—they travel up and down through the DOM tree to reach their target.
This is known as event propagation: a mechanism that defines how events propagate or travel through the DOM tree to arrive at its target and what happens to it afterward.
The concept of event propagation was introduced to deal with the situations in which multiple elements in the DOM hierarchy with a parent-child relationship have event handlers for the same event, such as a mouse click. Now, the question is which element's click event will be handled first when the user clicks on the inner element: the click event of the outer element, or the inner element?
Event Propagation has three phases:
- Capturing Phase - the event starts from the
window
down until it reaches theevent.target
. - Target Phase - the event has reached the
event.target
. The most deeply nested element that caused the event is called a target element, accessible asevent.target
. - Bubbling Phase - the event bubbles up from the
event.target
element up until it reaches thewindow
, meaning: when an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors. That's the reverse of what is happening in the Capturing Phase.
Event Bubbling and Capturing in React
Bubbling and capturing are both supported by React in the same way as described by the DOM spec, except for how you go about attaching handlers.
Bubbling is as straightforward as with the normal DOM API; simply attach a handler to an eventual parent of an element, and any events triggered on that element will bubble to the parent, just like in our example in the beginning.
Capturing is just as straightforward, but instead of the onClick
prop, you have to use onClickCapture
on your element.
How Do You Stop an Event from Bubbling/Capturing?
Going back to our original example, how can we make sure our counter is only incremented by 1 when we click the button?
The answer is using stopPropagation()
This method of the Event
interface prevents further propagation of the current event in the capturing and bubbling phases.
It does not, however, prevent any default behaviors from occurring. (if you want to stop those behaviors, you would need to use the preventDefault()
method)
If we change our code to:
const [counter, setCounter] = useState(0);
return (
<div onClick={() => setCounter((prevCounter) => prevCounter + 1)}>
<button
onClick={(event) => {
event.stopPropagation();
setCounter((prevCounter) => {
return prevCounter + 1;
});
}}
>
Counter value is: {counter}
</button>
</div>
Our counter will be incremented by 1 each time we click the button, thanks to event.stopPropagation()
which prevents the event from bubbling up to button
's parent and triggering the parent's onClick
as well.
However, be careful when stopping events from propagating, because sometimes you can’t really be sure you won’t be needing the event above in one of the element's parents, maybe for completely different things.
If this is the case, one alternative to stopping the propagation is writing your data into the event object in one handler and read it in another one, so you can pass to handlers on parents information about the processing below.
Happy coding!! 🚀
Top comments (5)
Very useful article! Can you explain further what you mean by "writing your data into the event object in one handler and read it in another one, so you can pass to handlers on parents [sic] information about the processing below"? Why wouldn't you just write the data and read it in a single handler?
I'm happy to hear you have found this article to be useful!
In regards to your question - imagine you have the following:
If you want the
onClick
of both thespan
anddiv
to be triggered when you click on thespan
, except for when it's thespan
withid
of2
, how can you achieve that?My suggestion was that one option is to write information to the event object of that
span
, so then thediv
event handler can read it, see that it's thespan
withid
of2
, and then not trigger its own event handler.I hope it makes sense!
How do you write information to the event object? With
event.someProp = value
?Yes, I now see the application, thank you!
Thanks!! This post saved me!