El Conflicto de Soberanía en el DOM
Integración de Google Maps en Phoenix LiveView: aislamiento de reactividad y patrones para evitar la destrucción del DOM por SDKs de terceros
Por Gonzalo Oviedo · Junio 2026 · Lectura de 10 min
Phoenix LiveView ha revolucionado el desarrollo web al permitir interfaces de usuario ricas y en tiempo real sin salir del ecosistema de Elixir. Sin embargo, cuando introducimos SDKs de JavaScript de terceros que toman control directo del DOM (como Google Maps, Google Places Autocomplete, Stripe o Chart.js), nos encontramos con un conflicto fundamental: ¿quién tiene la soberanía sobre el DOM? ¿El motor de diffs de LiveView o el script de JavaScript del cliente?
El Conflicto Fundamental del DOM
Section titled “ El Conflicto Fundamental del DOM”Por diseño, Phoenix LiveView mantiene un árbol virtual del DOM en el servidor, calcula los cambios mínimos (diffs) ante cualquier actualización de estado y los envía al cliente mediante WebSockets. El cliente (a través de la biblioteca morphdom) aplica estos parches directamente sobre el DOM real de forma quirúrgica.
Por otro lado, SDKs como Google Maps o Google Places Autocomplete operan bajo la filosofía opuesta: una vez cargados, se adueñan de un elemento HTML, inyectan su propia estructura compleja de canvas, inputs y listeners, y esperan que nadie más toque ese fragmento del DOM.
Si LiveView parchea el DOM sobre un área controlada por un SDK externo sin precauciones, destruirá instantáneamente el estado interno de la biblioteca JavaScript, rompiendo la interactividad.
El Escenario: Configuración de Preferencias de Viaje
Section titled “ El Escenario: Configuración de Preferencias de Viaje”Imaginemos una pantalla típica de una aplicación de carpooling (TeLlevo) donde el pasajero configura sus preferencias de viaje de Ida y Vuelta. La interfaz ofrece:
- Un mapa interactivo de Google Maps que traza la ruta en tiempo real.
- Dos inputs de dirección (Origen y Destino) integrados con Google Places Autocomplete.
- Un selector de horario de salida (Horas y Minutos) de 24 horas.
- Un selector de días de la semana de viaje.
Para separar la Ida de la Vuelta, la interfaz utiliza pestañas (Tabs). Cambiar de pestaña debe actualizar los valores del formulario de forma reactiva en el servidor para reflejar los datos almacenados de cada sentido.
El Antipatrón: El Formulario Reactivo Global
Section titled “ El Antipatrón: El Formulario Reactivo Global”La solución intuitiva (y la que comúnmente proponen las IAs generativas de código) es envolver toda la interfaz dentro de una gran etiqueta de formulario de Phoenix LiveView:
<form phx-change="form_changed" phx-submit="save"> <div id="map-container" phx-hook="GoogleMap" phx-update="ignore"></div> <input id="origin-input" value={@origin_address} /> <input id="destination-input" value={@destination_address} /> <select name="hora_hh">...</select> <select name="hora_mm">...</select> <button type="submit">Guardar</button></form>A primera vista, esto parece correcto. Sin embargo, introduce un acoplamiento reactivo que rompe la aplicación de dos formas catastróficas:
La Solución: Aislamiento de la Reactividad
Section titled “ La Solución: Aislamiento de la Reactividad”La solución elegante consiste en aplicar el principio de Aislamiento de Reactividad. En lugar de forzar a que toda la vista sea un único formulario gigante, dividimos la interfaz en zonas de soberanía independientes:
- Zona Libre de Formularios (Mapa y Direcciones): El contenedor del mapa y las entradas de dirección se colocan en contenedores
<div>estándar, completamente fuera de cualquier formulario de Phoenix. Escribir en las direcciones no gatilla ningún evento de LiveView, permitiendo que el SDK de Google Places mantenga el control exclusivo y nativo de los inputs. - Zona de Formulario Aislado (Horario): Envolvemos únicamente los selectores de hora y minuto dentro de un formulario local de LiveView. Cuando el usuario cambia la hora, solo este pequeño subárbol se actualiza, dejando el mapa y las direcciones completamente intactos.
- Envío Directo con phx-click: El botón de guardar se sitúa fuera del formulario y utiliza
phx-click="save", gatillando el guardado de forma limpia y directa mediante el envío de los parámetros almacenados en el estado del socket.
Implementación de Código
Section titled “ Implementación de Código”A continuación se presenta la implementación de la LiveView en Elixir utilizando este patrón de aislamiento:
El Misterioso Fallo de la Navegación SPA (live_navigate)
Section titled “ El Misterioso Fallo de la Navegación SPA (live_navigate)”Incluso aplicando el aislamiento de reactividad, muchos desarrolladores se topan con un bug fantasma: el mapa funciona perfectamente la primera vez que se entra a la pantalla, pero si presionas “Volver” y regresas, se rompe por completo.
Este comportamiento errático se debe a una regla de seguridad e interactividad crítica de los navegadores modernos al interactuar con el ciclo de vida de Phoenix LiveView:
La Solución Definitiva: Carga Dinámica en el Hook JS
Section titled “La Solución Definitiva: Carga Dinámica en el Hook JS”Para resolver esto de forma definitiva y robusta ante cualquier flujo de navegación, se deben aplicar dos cambios de diseño:
A. Eliminar los scripts manuales del HEEx — Elimina por completo la etiqueta <script> manual de tus plantillas LiveView. Deja que el hook JS sea el único responsable de cargar el SDK de forma dinámica usando document.createElement("script") (el cual los navegadores sí ejecutan de forma garantizada).
B. Optimizar los botones de retorno con Navegación SPA — Reemplaza todos los botones “Volver” por <.link navigate={@to}>. Esto evita recargas completas del navegador al navegar hacia atrás, manteniendo window.google en memoria y haciendo que re-ingresar a cualquier pantalla con mapas sea instantáneo y libre de llamadas de red adicionales.
Reglas de Oro para Integraciones en LiveView
Section titled “ Reglas de Oro para Integraciones en LiveView”Al integrar cualquier SDK de JavaScript de terceros que manipule el DOM, sigue estas tres directrices fundamentales para evitar conflictos de soberanía:
- Aísla los inputs dinámicos — Nunca coloques inputs vinculados a autocompletados o widgets externos dentro de formularios reactivos globales con
phx-change. El re-renderizado destruirá sus event listeners. - Usa phx-update=“ignore” con un ID único — Cualquier contenedor de mapas o gráficos (canvas) debe llevar
phx-update="ignore"y unidpersistente para que el motor de parches de LiveView lo preserve intacto en las actualizaciones. - Encapsula la reactividad con formularios acotados — Si necesitas selectores reactivos (como horas, categorías o filtros), envuélvelos en su propio componente
<.form>aislado. Esto limita el alcance del re-renderizado únicamente a ese pequeño fragmento del árbol del DOM.
Conclusión
Section titled “Conclusión”La integración de SDKs interactivos de JavaScript en Phoenix LiveView no tiene por qué ser una fuente de dolores de cabeza o parches inestables en el cliente. Al comprender el ciclo de vida de los parches del DOM de LiveView y aplicar el patrón de Aislamiento de Reactividad, podemos construir interfaces web sumamente complejas, rápidas y robustas que combinan lo mejor de dos mundos: la simplicidad del desarrollo del lado del servidor con Elixir y la potencia interactiva de las mejores herramientas de JavaScript en el cliente.