I had the weirdest problem with LiveView's change tracking when name attribute has value id

I started getting duplicate form elements on DOM updates with leex markup like below and I fixed it by changing hidden input’s name attribute from id to player_id. What I mean is that I clicked New card button and new form element would added to DOM (a duplicate) for some player rows. I also added id attribute for form and didn’t help and there where multiple forms with same id. Does input with name attribute with value of id have some sort of special meaning in LiveView and is it documented somewhere?

While writing this found similar post from this forum without any replies LiveView has a problem with input name id

<%= for player <- @players do %>
        <tr style="<%= if player.id == @selected_player_id, do: "background-color: lightgreen;", else: "" %>">
          <td><button id="save_selected-player-<%= player.id %>" class="button is-small is-success" phx-click="select_player" phx-value-id="<%= player.id %>" phx-hook="SaveSelectedPlayerId">Select</button></td>
          <td><%= player.name %></td>
          <td>
            <form phx-change="change_player_edge">
              <input name="id" type="hidden" value="<%= player.id %>" />
              <div class="select is-small">
                <select name="edge">
                  <option value=""></option>
                  <option value="hesitant" <%= if player.edge == :hesitant, do: "selected", else: "" %>>Hesitant</option>
                  <option value="quick" <%= if player.edge == :quick, do: "selected", else: "" %>>Quick</option>
                  <option value="level_headed" <%= if player.edge == :level_headed, do: "selected", else: "" %>>Level Headed</option>
                  <option value="imp_level_headed" <%= if player.edge == :imp_level_headed, do: "selected", else: "" %>>Imp. Level Headed</option>
                  <option value="quick_and_level_headed" <%= if player.edge == :quick_and_level_headed, do: "selected", else: "" %>>Quick + Level Headed</option>
                  <option value="quick_and_imp_level_headed" <%= if player.edge == :quick_and_imp_level_headed, do: "selected", else: "" %>>Quick + Imp. Level Headed</option>
                </select>
              </div>
            </form>
          </td>
          <td>
            <% card = get_card_for_player(player) %>
            <%= case card do %>
              <% nil -> %>
              <% %{suit: nil} -> %>
                <span>Joker</span>
              <% %{suit: :spades} -> %>
                <span><%= format_card(card) %> </span><img src="<%= Routes.static_path(@socket, "/images/cards/spades.svg") %>" style="width:11px" />
              <% %{suit: :hearts} -> %>
                <span><%= format_card(card) %> </span><img src="<%= Routes.static_path(@socket, "/images/cards/hearts.svg") %>" style="width:11px" />
              <% %{suit: :diamonds} -> %>
                <span><%= format_card(card) %> </span><img src="<%= Routes.static_path(@socket, "/images/cards/diamonds.svg") %>" style="width:11px" />
              <% %{suit: :clubs} -> %>
                <span><%= format_card(card) %> </span><img src="<%= Routes.static_path(@socket, "/images/cards/clubs.svg") %>" style="width:11px" />
            <% end %>
            <span style="cursor:pointer"><%= format_more_cards(player.cards) %></span>
          </td>
          <td><button class="button is-small mr-1" phx-click="add_card_for_player" phx-value-id="<%= player.id %>" data-confirm="Do you really want to give new card for player &quot;<%= player.name %>&quot;?">New card</button><button class="button is-small" phx-click="remove_player" phx-value-id="<%= player.id %>" data-confirm="Do you really want to remove player &quot;<%= player.name %>&quot;?">Remove</button></td>
        </tr>
      <% end %>```
<%= for player <- @players do %>
<% end %>

Problems like these are usually caused by duplicate IDs in the DOM. So that’s the first thing I would look at. If you enable debug mode on your console, you should see things logged.

2 Likes

There where no errors in the console and everything works fine after changing <input name="id" type="hidden" value="<%= player.id %>" /> to <input name="player_id" type="hidden" value="<%= player.id %>"/>
That’s why I was asking if name attribute with value id has some sort of special meaning in LiveView.

Oh it’s an morphdom issue morphdom vs. named fields

morphdom vs. named fields
<input name="id"> results in the surrounding form being duplicated rather than replaced.
This is because if such a field exists within a form, form.id returns that DOM node rather than the form’s id attribute value.

From GitHub - patrick-steele-idem/morphdom: Fast and lightweight DOM diffing/patching (no virtual DOM needed)

getNodeKey (Function(node)) - Called to get the Node’s unique identifier. This is used by morphdom to rearrange elements rather than creating and destroying an element that already exists. This defaults to using the Node’s id property. (Note that form fields must not have a name corresponding to forms’ DOM properties, e.g. id.)

11 Likes

Quite the trap, that one!

3 Likes

Quite quite, we just ran into it

1 Like

quite quite quite.

1 Like

Still an issue in 2023, just bumped into it. Quite tricksy.

(Also worth noting that the bug didn’t appear in dev - only in prod. Not sure why.)

Same, so glad I found this thread/the reason. A bajillion thanks!

(Also worth noting that I ran into this in dev, not prod :upside_down_face: - LiveView 0.19.3)


Edit (adding this for searchability):
Using field={@form[:id]} or name="id" was causing other inputs in my table of Phoenix stream elements to disappear

1 Like