When LiveView updates an element's phx-click and phx-value attributes, the values are updated in the DOM, and click event triggers with new values.
When LiveView updates an element's phx-click and phx-value attributes, the values are updated in the DOM, but the click event triggers as if the the original values were still there.
Example: clicking the first link works as expected - but the second triggers "show-something" rather than "show-something-else".
defmodule AppWeb.ExampleLive do
use Phoenix.LiveView
def render(assigns) do
~L"""
<%= cond do %>
<% @example-> %>
<%= link to: "#", "phx-click": "show-something", "phx-value": @example.id do %>
Link
<% end %>
<% @another -> %>
<%= link to: "#", "phx-click": "show-something-else", "phx-value": @another.id do %>
Link
<% end %>
<% true -> %>
Lobby
<% end %>
"""
end
def mount(%{example: example}, socket) do
{:ok, assign(socket, example: example, another: nil)}
end
def handle_event("show-something", id, socket) do
example = Example.get_example(id)
{:noreply, assign(socket, example: example, another: nil)}
end
def handle_event("show-something-else", id, socket) do
another = Another.get_another(id)
{:noreply, assign(socket, example: nil, another: another)}
end
end
I experienced exactly the same issue yesterday.
A possibly related quirk:
1) Add a second link the the second cond above
2) Move from first cond to second cond via LiveView
3) When showing the second cond, the first link triggers the action from the link in the first cond
4) The second link works as expected
def render(assigns) do
~L"""
<%= cond do %>
<% @example-> %>
<%= link to: "#", "phx-click": "show-something", "phx-value": @example.id do %>
Link
<% end %>
<% @another -> %>
<%= link to: "#", "phx-click": "unrelated-action" do %>
First link
<% end %>
<%= link to: "#", "phx-click": "show-something-else", "phx-value": @another.id do %>
Second link
<% end %>
<% true -> %>
Lobby
<% end %>
"""
end
Update: Adding an id attribute to either the changed element or topmost element in new cond is a temporary fix. ID probably triggers some sort of forced refresh?
It looks like a "click" event listener is bound to the link with a phxEvent="show-something" here:
https://github.com/phoenixframework/phoenix_live_view/blob/d77f787356ee312eb8a8f8427c03da29a6010abe/assets/js/phoenix_live_view.js#L677-L680
Looking at the code, I expected a new "click" event listener with a phxEvent="show-something-else" to be bound via a call to maybeBindAddedNode:
https://github.com/phoenixframework/phoenix_live_view/blob/d77f787356ee312eb8a8f8427c03da29a6010abe/assets/js/phoenix_live_view.js#L752-L760
But in this case, the existing link element is updated, as you can see when this method is called:
https://github.com/phoenixframework/phoenix_live_view/blob/d77f787356ee312eb8a8f8427c03da29a6010abe/assets/js/phoenix_live_view.js#L426
And bindClick is never called, and the existing event listener with the old phxEvent remains.
If you add an id attribute, as suggested in the previous comment, the link element is removed and a new one is added, causing this code to run:
https://github.com/phoenixframework/phoenix_live_view/blob/d77f787356ee312eb8a8f8427c03da29a6010abe/assets/js/phoenix_live_view.js#L411-L418
which calls view.maybeBindAddedNode and binds "click" with a phxEvent="show-something-else"
I don't see any code that would add event listeners in the case of updated elements.
This is not a solution, as it leaves behind the old event listeners, but you get the expected behavior by adding:
onElUpdated: function(toEl) {
view.maybeBindAddedNode(toEl)
},
I came across the same issue today.
I tried using the ID as suggested above, but it had a quirk.
Here's the code I'm running in my .leex file
<%= if @current_link && @current_link.id == link.id do %>
<%= link "Cancel", to: "#", phx_click: "cancel", id: "cancel-link" %>
<% else %>
<%= link "Edit", to: "#", phx_click: "edit", phx_value: link.id, id: "edit-link" %>
<% end %>
If I click on the edit link, the cancel link shows up, but the edit link will stay on the screen.
I was able to get around it for now by wrapping one of the links in p tags.
<%= if @current_link && @current_link.id == link.id do %>
<p><%= link "Cancel", to: "#", phx_click: "cancel" %></p>
<% else %>
<%= link "Edit", to: "#", phx_click: "edit", phx_value: link.id %>
<% end %>
Hope this info helps.
I ran into an issue similar to this that might be related (happy to open a new issue if requested).
Basically, I have a LiveView with a table and some pagination links like so:
<a href="#"
data-behavior="prevent-default"
phx-click="show_all"
>Show all?</a>
<table>
<th>ID</th>
<th>Title</th>
<%= for user <- @users %>
<tr>
<td><%= user.id %></td>
<td><%= user.title %></td>
</tr>
<% end %>
<tr>
</table>
<%= for page <- @pages %>
<a href="#"
class="paging__link"
data-behavior="prevent-default"
phx-click="page"
phx-value-page="<%= page %>"
><%= page %></a>
<% end %>
In this case, the "show-all" event changes the data of the table, swaps the current page out for a different set of data.
I'm able to reliably recreate the issue in this case. It seems when elements earlier in the DOM are changed the links will randomly work improperly.
It seems like when they get updated a second url event is sent to the LiveView which is overwriting the updated page param, so clicking the "next" link takes you back to the same page.
I tried the id patch mentioned here which didn't work, but changing the tags from a to span did do the trick.
Most helpful comment
Update: Adding an
idattribute to either the changed element or topmost element in new cond is a temporary fix. ID probably triggers some sort of forced refresh?