Dispatching Custom Events
Table of contents
We are used to listen to events and react to them: I trigger this function when a click
happens here or when a user scroll
s down or after the resize
fired because the viewport has changed. But we are not used to emit our own events.
Sometimes we need to know the current state of an API asynchronus task or if a third-party library has performed an action or we want to communicate different parts of our application or components due to an user interaction need to trigger a secondary effect. For instance, an user click
s on a add to cart button and the badge over the cart icon increments its value by 1. How these parts or components are connected depends on the moment in which this feature araises, it can vary if more than one components need to know about the global state or it depends on the stack your are using. A solution could be having a global state and make those components subscribe to it or effortlessly, emit a custom event and react to it.
We are going to review a atipical use case, in combination with React and GSAP.
Knowing React nor GSAP is needed to understand the example nor the article but it will make the code clearer.
Starting point
We have two full-width siblings components, one above the other.
<Parent>
<ComponentA />
<ComponentB />
</Parent>
In this case <ComponentB />
is pinned when it reaches the top of the viewport and then runs a nice animation ✨ So in the initial load of the document <ComponentB />
is going to calculate when it will reach the top to anchor and start its animation, taking advance of its position in the document. But of course, before it reaches the top the user clicks on <ComponentA />
and changes its content (adding or deleting) so its height is different and consequently changes the current position of <ComponentB />
within the document, making the initial calculations no longer match the current reality 🪄.
Solutions
As I said before, we could create a context and subscribe to it so we will know when that click
ocurrs, or set a global state or useResizeObserver
.
But if we use ResizeObserver
this article should be named “how to use ResizeObserver
” and it is not, so we will see how to work with custom events. Furthermore, using ResizeObserver
we will know that <ComponentA />
has changed but <ComponentB />
still be unaware of that :P
Custom Events
There are three steps:
- Create an event
- Dispatch the event
- Listen and react to the event
Create an event
Using the constuctor CustomEvent
we can create an event. It expects two parameters being the second optional.
const event = new CustomEvent(type[, options]);
The first parameter is a string
and it specifies the name (type) of the event, the second one is an object
and it is useful to set the options the event needs. In addition to the options available from Event
we can use the property detail
which it is very useful to attach key information to the event itself.
const componentHasChanged = new CustomEvent('refreshScrollTrigger', {
bubbles: true, // From Event. Makes the event bubble through the DOM
detail: {
count: 1,
},
})
Dispatch the event
Once the event is created, we need to dispatch it (like in the novel Contact by Carl Sagan) so other components can listen and react to it.
The event can be directly dispateched.
element.dispatch(componentHasChanged)
Or associated to another event.
element.dispatch('click', (event) => element.dispatch(componentHasChanged))
Or depending if a property changes
useEffect(() =>
element.dispatch(componentHasChanged);
[content]);
Listen and react to the event
We are going to use addEventListener
method passing it the name of the event and the callback that will be the function that will refresh the calculation of its position.
// The animation
const tl = gsap.timeline(/*...*/).to(/*...*/)
// The function that updates the the position that the element occupies
const updateScrollTrigger = () => tl.scrollTrigger.refresh()
// The mechanisms to listen to the event
window.addEventListener('refreshScrollTrigger', updateScrollTrigger)
If we need to access to the detail
property we do straight trought the event:
const updateScrollTrigger = (event) => {
if (event.detail.count > 0) {
tl.scrollTrigger.refresh()
}
}
CustomEvent vs Event
Could we used the Event
constructor instead of the CustomEvent
one? Yes, we can consider them interchangeable since CustomEvent
is based on Event
but it is worth remembering that CustomEvent
can use the detail
property to add information along with the event, which could be anything, such as the value of an attribute.
const event = new CustomEvent('awesomeEvent', {
detail: element.dataset.title,
})
On the other hand, it makes sense to use Event
for standard envet types and CustomEvent
for those tailored or custom.
A note of caution
Earlier we talked about other possible solutions and one of the reasons for not choosing CustomEvent
is that since through these events are triggering functions that have side effects, we are hiding a little bit the need for some parts to communicate, which is a little tricky and due to we could be hiding, for example, that our componentization has not been correct*.*
Conclusion
Knowing all the features your application needs beforehand allows you to make smarter decisions. Maybe you don’t need a very big, structured and solid solution but something small, flexible and exceptional. It also happens from time to time that the product is very advanced and it is “too late” to add new features 🤷 Knowing when it is convenient to use one tool or another is a knowledge that from the outside seems innate but often is the result of practice. So practice and try all the new solutions you can think of, because that’s where creativity lives.
Resources
comments powered by DisqusIf you find it interesting
If you have any doubt or you want to talk about this topic, if the content or our profiles are interesting to you and you think we could something together, do not hesitate to contact us on twitter or trough the email address hola@mamutlove.com