Originally reported by @Gazler in Discord:
Mix.install([
{:phoenix_playground, "~> 0.1.6"},
{:phoenix_live_view, "~> 1.0.0", override: true},
])
defmodule DemoLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
send(self(), :change_id)
end
socket =
socket
|> assign(id: 1, counter: 0)
|> push_event("myevent", %{})
{:ok, socket}
end
def handle_info(:change_id, socket) do
{:noreply, assign(socket, id: 2)}
end
def handle_event("lol", _params, socket) do
{:noreply, socket}
end
def handle_event("reload", _params, socket) do
counter = socket.assigns.counter + 1
socket =
socket
|> push_event("myevent", %{})
|> assign(counter: counter)
socket =
if counter > 4096 do
push_navigate(socket, to: "/")
else
socket
end
{:noreply, socket}
end
def render(assigns) do
~H"""
<div id="main" phx-hook="OuterHook">
<div phx-hook="InnerHook" id={"id-#{@id}"} />
This is an example of nested hooks resulting in a "ghost" element
that isn't on the DOM, and is never cleaned up. In this specific example
a timeout is used to show how the number of events being sent to the server
grows exponentially.
<p>Doing any of the following things fixes it:</p>
<ol>
<li>Setting the `phx-hook` to use a fixed id.</li>
<li>Removing the `pushEvent` from the OuterHook `mounted` callback.</li>
<li>Deferring the pushEvent by wrapping it in a setTimeout.</li>
</ol>
</div>
<div>
To prevent blowing up your computer, the page will reload after 4096 events, which takes ~12 seconds
</div>
<div style="color: blue; font-size: 20px" id="counter">Total Event Calls: {@counter}</div>
<div style="color: red; font-size: 72px" id="notice" phx-update="ignore">I will disappear if the bug is not present.</div>
<script>
window.hooks.OuterHook = {
mounted() {
this.pushEvent("lol")
},
}
window.hooks.InnerHook = {
mounted() {
console.log("MOUNTED", this.el);
this.handleEvent('myevent', this._handleEvent(this));
},
destroyed() {
document.getElementById("notice").innerHTML = "";
console.log("DESTROYED", this.el);
},
_handleEvent(self) {
return () => {
setTimeout(() => {
console.warn("reloading", self.el);
self.pushEvent("reload", {})
}, 1000)
}
}
}
</script>
"""
end
end
PhoenixPlayground.start(live: DemoLive, port: 4200)
The hook element has a dynamic ID, but when it changes, the old hook is not cleared, causing events to exponentially increase.
Originally reported by @Gazler in Discord:
The hook element has a dynamic ID, but when it changes, the old hook is not cleared, causing events to exponentially increase.