October 01, 2020
I first learned about event delegation when I went to Galvanize back in 2016. Unfortunately, it was taught when I was feeling rather overloaded by all the information being hurled at me during this intensive six month program. I kind of got it, but not completely. It wasn't until a few months into my first job when I had the chance to work with React in the real world that I found a need to revisit the concept. I had multiple click events attached to several children and was noticing some weird things happening when trying to handle the event. Why?
What is Event Delegation?
Event delegation is a process used to handle events in a level higher up in the DOM than where the event actually occurred. This is very powerful as it means you can create a single event listener on a parent node that will handle events fired on any of its children. Without event delegation, each event would need to have its own event listener which would make monitoring events across an application far less streamlined and performant.
Imagine if you wanted to add a table to a page and needed to track a click event on all the individual cells.
You could add event handlers to every single cell.
But that's definitely not optimal and will only get worse with every cell you need to add. Let's look at an updated table where the event delegation pattern has been applied.
Now, from within the handler, you can access the event and whatever information is attached to it. For instance, we can now access the text of every cell.
As you can see, event delegation is incredibly powerful and makes it easy for us to write performant, DRY code.
How Event Delegation Works
The event listener on the parent is always lying in wait, watching over all of its children and their events, waiting to respond when an event is fired somewhere in the DOM.
When an event is fired on an element, now known as the target element, the process of event delegation begins. This event handling pattern is made up of two processes: event bubbling and event capturing.
This is the most commonly used of the two processes. It's called bubbling because if you visualize a bubble in the water as it makes its way to the surface, it becomes really easy for you to then visualize an event being fired on an element, bubbling up to its parent, then up to its ancestors, and then continuing its journey all the way up to the document object. Apart from the focus and blur events, which trigger on an element focusing/losing focus, all events that fire will bubble in this fashion.
In the following screenshot we can imagine a user clicking on one of the cells, the event bubbling up to its parent row (tr), and then continuing to bubble up to the table element where we have placed the
handleCellsClickevent handler. The table element is the common ancestor for all the table cells.
When an event is fired on an element, the parent's handler is able to get more details about where the event occurred by utilizing
Now let's say we wanted to get specific event information from the first row but then different event data for all remaining rows. For the second row onwards, we could still utilize our
handleCellsClickevent handler that we attached to the table element. For the first row, we'd want to add a separate handler.
The issue with this right now is that anytime someone clicks on a cell in the first row both the
handleCellsClickevents would fire.
This happens because when the target element is clicked, in this case cell 3, the event bubbles up to the
handleRowClickevent handler on the
trelement, and then continues on to the
handleCellsClickevent handler on the table element.
In order to prevent the event from bubbling to
handleCellsClick, which is an event handler higher up in the DOM tree, we can simply add
We can see our log coming through from
handleRowClickbut we no longer see the log coming from
handleCellsClickas the bubbling is now being stopped at the row element thanks to
An important thing to keep in mind is that unnecessary use of
event.stopPropagationcan cause issues for analytics. If you're effectively blocking the bubbling from reaching the surface at certain points in your application, certain analytics, such as user click count, won't be able to report the complete picture. Apply you propagation stopping wisely :)
What is Event Capturing?
In the simplest sense, capturing is the opposite of bubbling where the event trickles down to the element to 'capture' the event on the targeted element. From here, the bubbling begins.
While as straightforward as bubbling, capturing is normally invisible to us and is rarely used the way we use bubbling to handle events. When someone clicks on a target element, for instance, the event will go down the ancestor chain to where the event was fired. We don't see this happen so, for those who are unfamiliar with event delegation and capturing, it may appear that the event actually starts on the element and simply bubbles up.
There aren't a whole lot of use cases as to why you would want to utilize the capturing case explicitly in your code. However, if you're hoping to listen to events that don't bubble, such as focus and blur, then you could do this during the capturing phase.
To register an event on the capture phase instead of the bubbling stage, you need to you need to append 'Capture' to the end of the event name.
You can see in this example, we've added an
onClickCaptureevent handler to the parent element. When the button is clicked, we only see 'Capturing the className of the target element: target-element' as the process stops inside our
handleCapturefunction. We are getting data for the target element from the capture phase and then preventing the bubbling process from occurring.
For comparison, if we change
onClickand then click the button, we see logs from both events in the console.
Custom Event Delegation
There's one other thing I want to touch on before wrapping up and that is custom event delegation. This is where you add custom data to the event object.
While this may seem like a useful way to pass data around, it is discouraged as it goes against React's philosophy. React has a one-way data flow and adding custom data to the event object introduces a secondary data flow. Having multiple data flows becomes problematic to manage as an application grows, and debugging errors that take place on the event is complicated. Additionally, instead of explicitly passing data by the way of props, you introduce an implicit dependency whereby data is also be passed on the event. Avoid doing this and stick to passing data the way it is designed to be passed.