Emitir Eventos a Medida
Tabla de contenido
Estamos acostumbrados a escuchar eventos y reaccionar a ellos: ejecuto una función cuando ocurre un click
aquí, o cuando un usuario hace scroll
allá o se dispara un resize
porque el viewport ha cambiado. Pero quizá no lo estamos tanto a emitir nuestros propios eventos.
A veces necesitamos saber el estado de una tarea asíncrona de una API, si una librería de terceros ha realizado una acción o queremos comunicar partes diferentes de una web, aplicación o componentes porque una acción de un usuario tiene que disparar un efecto secundario en otro lugar. Por ejemplo, un usuario hace click
en un botón para añadir un producto al carrito y el contador sobre el icono del carrito actualiza su valor sumándole 1. Cómo estas partes o componentes se comunican puede estar establecida de diferentes formas dependiendo del momento en el que surge la necesidad, de si van a haber más componentes dependientes de un estado global o del stack que estés utilizando. Una solución podría ser tener un estado global y subscribir algunos componentes a éste o con menor esfuerzo, emitir un evento a medida y reaccionar cuando ocurra.
Vamos a ver un caso de uso menos típico, combinándolo con React y GSAP.
No hace falta saber React ni GSAP para entender el ejemplo o el artículo pero es más claro si conoces un poquito de ambas librerías.
Punto de partida
Tenemos dos componentes hermanos que ocupan todo el ancho de la pantalla y por lo tanto uno está encima del otro.
<Parent>
<ComponentA />
<ComponentB />
</Parent>
Se da la particularidad de que <ComponentB />
se ancla (se pinnea) **cuando alcanza la parte superior del viewport y a continuación ejecuta una bonita animación ✨ De modo que en la carga inicial del documento <ComponentB />
va a calcular cuándo va a alcanzar la parte superior para anclarse e iniciar su animación, partiendo de su posición en el documento. Pero claro, antes de que llegue arriba el usuario hace un click
en <ComponentA />
y cambia su contenido (*poniendo y quitando*) de modo que su altura es diferente y en consecuencia cambia la posición de <ComponentB />
dentro del documento, haciendo que los cálculos iniciales ya no coincidan con la realidad actual 🪄
Posibles soluciones
Como dijimos, podríamos crear un contexto y subscribimos para saber cuándo ese click
ha ocurrido, o tener un estado global de la aplicación, o podemos usar ResizeObserver
.
Pero si usásemos ResizeObserver
este artículo se llamaría “cómo usar ResizeObserver
” y como no es el caso pues vamos con los eventos a medida. Además, sabríamos que <ComponentA />
ha cambiado pero <ComponentB />
seguiría sin saberlo :P
Custom Events
El mecanismo tiene tres pasos:
- Crear un evento
- Disparar el evento
- Escuchar y reaccionar al evento
Crear un evento
Utilizando el constructor CustomEvent
podemos crear un evento. Espera dos parámetros aunque el segundo es opcional.
const event = new CustomEvent(type[, options]);
El primero es un string
y sirve para indicar el nombre (tipo) del evento y el segundo es un object
y sirve para detallar las opciones del evento. Además de todas las opciones disponibles para Event
, puede usar detail
que es muy útil para enviar información asociada al evento y que queramos conocer al otro lado.
const componentHasChanged = new CustomEvent('refreshScrollTrigger', {
bubbles: true, // Proviene de Event. Permite que el evento se propague
detail: {
count: 1,
},
})
Disparar el evento
Una vez creado, necesitamos enviarlo para que pueda ser escuchado (como en Contact de Carl Sagan) y ejecutar una función después.
Se puede disparar directamente
element.dispatch(componentHasChanged)
O vinculado a otro evento.
element.dispatch('click', (event) => element.dispatch(componentHasChanged))
O si una propiedad cambia
useEffect(() =>
element.dispatch(componentHasChanged);
[content]);
Escuchar y reaccionar al evento
Vamos a usar addEventListener
pasándole el nombre del evento y el callback que será la función refrescará el cálculo de su posición.
// La animación
const tl = gsap.timeline(/*...*/).to(/*...*/)
// La función que actualiza el conocimiento sobre la posición que el elemento ocupa
const updateScrollTrigger = () => tl.scrollTrigger.refresh()
// El mecanismo para escuchar el evento
window.addEventListener('refreshScrollTrigger', updateScrollTrigger)
En el caso de necesitar acceder a detail
podemos hacerlo directamente desde el evento:
const updateScrollTrigger = (event) => {
if (event.detail.count > 0) {
tl.scrollTrigger.refresh()
}
}
CustomEvent vs Event
¿Podríamos haber utilizado Event
en lugar de CustomEvent
? Sí, hasta cierto punto podemos considerarlos intercambiables ya que CustomEvent
se basa en Event
pero conviene recordar la diferencia de que CustomEvent
puede usar la propiedad detail
para añadir información junto con el evento, que podría ser cualquier cosa, como el valor de un atributo.
const event = new CustomEvent('awesomeEvent', {
detail: element.dataset.title,
})
Por otro lado, tiene sentido que usemos Event
para los eventos conocidos y ya que ya están implementados y conoce el navegador y CustomEvent
para los que sean a medida.
Una nota de precaución
Antes hablábamos de otras posibles soluciones y es que uno de los motivos para no elegir CustomEvent
es que como a través de estos eventos estamos disparando funciones que tiene efectos secundarios, estamos escondiendo un poco la necesidad de que algunas partes se comuniquen, lo cuál es un poco tricky y podríamos estar escondiendo, por ejemplo, que nuestra componentización no ha sido la adecuada*.*
Conclusión
Conocer todas las necesidades de lo que estás construyendo con antelación te permite tomar mejores decisiones, y tal vez no necesitas una solución muy grande, estructurada y sólida sino algo pequeño, flexible y excepcional. También ocurre de vez en cuando que el producto está muy avanzado y es “demasiado tarde” para añadir nuevas funcionalidades 🤷 Saber cuándo conviene usar una herramienta u otra es un conocimiento que desde fuera parece innato pero a menudo es el resultado de la práctica. Así que practica y prueba todas las soluciones nuevas que se te ocurran, ahí vive la creatividad.
Recursos
comments powered by DisqusSi te ha parecido interesante
Tanto si tienes alguna duda o quieres charlar sobre este tema, como si el contenido o nuestros perfiles te parecen interesantes y crees que pdemos hacer algo juntos, no dudes en ponerte en contacto con nosotros a través de twitter o en el email hola@mamutlove.com