Duplicate DOM nodes when using `phx-remove` with `JS.hide` and transition.
Environment
- Elixir version (elixir -v): 1.13.4
- Phoenix version (mix deps): 1.6.11
- Phoenix LiveView version (mix deps): 0.17.11
- Operating system: macOS
- Browsers you attempted to reproduce this bug on (the more the merrier): Chrome
- Does the problem persist after removing "assets/node_modules" and trying again? Yes/no: Yes
Actual behavior
The setup is:
div element with a dynamic id, and a phx-remove with a JS.hide call. In addition there is a button
which has a phx-click="next" which changes the id.
This would cause the old element to be replaced with a new element. However, if you add a transition and click the button during the transition, then additional DOM nodes will be created.
Waiting until the transition finishes and clicking the button again, restores the DOM to it's proper state.
def handle_event("next", _params, socket) do
{:noreply, assign(socket, index: socket.assigns.index + 1)}
end
def render(assigns) do
~H"""
<div
id={"test-#{@index}"}
class="w-24 h-24 bg-indigo-800 rounded-lg flex justify-center items-center"
phx-remove={Phoenix.LiveView.JS.hide(transition: "some-transition-class", time: 2000)}
>
<span class="text-xl"><%= @index %></span>
</div>
<button type="button" class="px-4 py-2 bg-slate-800 rounded" phx-click="next">
Next
</button>
"""
end
Here a short gif demonstrating the behaviour. Check the DOM on the right:

Expected behavior
I would expect the DOM to only contain the current visible element and not the elements, which have been hidden by JS.hide.
does liveSocket.enableDebug() on the js console report any issues when this happens?
Not sure if it helps, but that's the output from the debug after clicking 3 times while it's transitioning. Looks good to me, as the diffs update only the two numeric values in the id attribute and in the node content.
// phx-FwX0rxZgHtuy2AAm mount: -
{
"0": "<a class=\"font-bold font-title\" data-phx-link=\"redirect\" data-phx-link-state=\"push\" href=\"/\">benvp</a>",
"1": "",
"2": "",
"3": {
"0": "0",
"1": " phx-remove=\"[["hide",{"time":2000,"to":null,"transition":[["some-transition-class"],[],[]]}]]\"",
"2": "0",
"s": [
"<div class=\"mt-12 max-w-screen-md m-auto flex justify-center space-y-4 flex-col items-center\">\n <div class=\"my-4 flex flex-wrap items-center justify-center space-x-4\">\n <div id=\"test-",
"\" class=\"w-24 h-24 bg-indigo-800 rounded-lg flex justify-center items-center\"",
">\n <span class=\"text-xl\">",
"</span>\n </div>\n </div>\n <button type=\"button\" class=\"px-4 py-2 bg-slate-800 rounded\" phx-click=\"next\">\n Next\n </button>\n</div>"
]
},
"s": [
"<header class=\"max-w-screen-md mx-auto px-8 lg:px-0\">\n <nav class=\"py-4 flex justify-between\">\n <div>\n",
"\n </div>\n\n <div class=\"space-x-6\">\n",
"\n",
"\n </div>\n </nav>\n\n <div class=\"-mx-[3%] w-[106%] h-[1px] bg-gradient-to-r from-transparent via-gray-500\"></div>\n</header>\n\n<main class=\"min-h-screen\">\n",
"\n</main>\n\n<footer class=\"max-w-screen-md mx-auto mt-20 pb-4 px-8 sm:px-0\">\n <div class=\"-mx-[3%] w-[106%] h-[1px] bg-gradient-to-r from-transparent via-gray-500\"></div>\n\n <div class=\"py-4 flex flex-col sm:flex-row justify-between items-center text-gray-400 text-xs\">\n <div class=\"flex items-center space-x-4\">\n <a class=\"w-[20px] font-bold font-title hover:text-white\" href=\"mailto:[email protected]?subject=Hi Ben\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z\"></path><path d=\"M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z\"></path></svg>\n </a>\n <a class=\"w-[20px] font-bold font-title hover:text-white\" href=\"https://github.com/benvp\" target=\"_blank\" rel=\"noreferrer\">\n <svg focusable=\"false\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 1.27a11 11 0 00-3.48 21.46c.55.09.73-.28.73-.55v-1.84c-3.03.64-3.67-1.46-3.67-1.46-.55-1.29-1.28-1.65-1.28-1.65-.92-.65.1-.65.1-.65 1.1 0 1.73 1.1 1.73 1.1.92 1.65 2.57 1.2 3.21.92a2 2 0 01.64-1.47c-2.47-.27-5.04-1.19-5.04-5.5 0-1.1.46-2.1 1.2-2.84a3.76 3.76 0 010-2.93s.91-.28 3.11 1.1c1.8-.49 3.7-.49 5.5 0 2.1-1.38 3.02-1.1 3.02-1.1a3.76 3.76 0 010 2.93c.83.74 1.2 1.74 1.2 2.94 0 4.21-2.57 5.13-5.04 5.4.45.37.82.92.82 2.02v3.03c0 .27.1.64.73.55A11 11 0 0012 1.27\"></path></svg>\n </a>\n <a class=\"w-[20px] font-bold font-title hover:text-white\" href=\"https://twitter.com/benvp_\" target=\"_blank\" rel=\"noreferrer\">\n <svg focusable=\"false\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22.46 6c-.77.35-1.6.58-2.46.69.88-.53 1.56-1.37 1.88-2.38-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29 0 .34.04.67.11.98C8.28 9.09 5.11 7.38 3 4.79c-.37.63-.58 1.37-.58 2.15 0 1.49.75 2.81 1.91 3.56-.71 0-1.37-.2-1.95-.5v.03c0 2.08 1.48 3.82 3.44 4.21a4.22 4.22 0 0 1-1.93.07 4.28 4.28 0 0 0 4 2.98 8.521 8.521 0 0 1-5.33 1.84c-.34 0-.68-.02-1.02-.06C3.44 20.29 5.7 21 8.12 21 16 21 20.33 14.46 20.33 8.79c0-.19 0-.37-.01-.56.84-.6 1.56-1.36 2.14-2.23z\"></path></svg>\n </a>\n </div>\n\n <p class=\"text-xs mt-2 sm:mt-0\">© 2022 Benjamin von Polheim • All rights reserved</p>\n </div>\n\n</footer>"
]
}
// phx-FwX0rxZgHtuy2AAm update
{
"3": {
"0": "1",
"2": "1"
}
}
// phx-FwX0rxZgHtuy2AAm update: -
{
"3": {
"0": "2",
"2": "2"
}
}
// phx-FwX0rxZgHtuy2AAm update: -
{
"3": {
"0": "3",
"2": "3"
}
}
And this is the HTML in the DOM. Somehow I end up three DOM nodes with all the differend ids test-1, test-2 and test-3
<div class="my-4 flex flex-wrap items-center justify-center space-x-4">
<div id="test-1" class="w-24 h-24 bg-indigo-800 rounded-lg flex justify-center items-center" phx-remove="[["hide",{"time":2000,"to":null,"transition":[["some-transition-class"],[],[]]}]]" style="display: none;">
<span class="text-xl">1</span>
</div><div id="test-2" class="w-24 h-24 bg-indigo-800 rounded-lg flex justify-center items-center" phx-remove="[["hide",{"time":2000,"to":null,"transition":[["some-transition-class"],[],[]]}]]" style="display: none;">
<span class="text-xl">2</span>
</div><div id="test-3" class="w-24 h-24 bg-indigo-800 rounded-lg flex justify-center items-center" phx-remove="[["hide",{"time":2000,"to":null,"transition":[["some-transition-class"],[],[]]}]]">
<span class="text-xl">3</span>
</div>
</div>
I tried to reproduce your issue and found that this was actually fixed in 0.18.2, specifically by 1abc20ca63331df984471c9b8a4fbf4a6cae0469 that introduces streams and also changed the handling of pending transitions.
If you still have issues, please send us a minimal reproduction (https://github.com/wojtekmach/mix_install_examples/blob/main/phoenix_live_view.exs can be used as basis) and we'll be happy to have another look. Thank you!
That's good news. Will dig into it again as soon as I find some time. 👍