I am observing the weird but reproducible behavior in LiveView
that a DOM element which should disappear after an LiveView
update still remain. I don’t have a minimal example, yet.
While writing this post I had an idea about a possible workaround, which turned out to be working. So I am posting this in the hope that people who observe the same thing will find it.
Here is just what I observe.
I have a LiveView
with as snippet similar to this in the heex. A simple loop over a list of items rendering checkboxes to check or uncheck the items.
<%= for {item, checked} <- @shown_items do %>
<div class="shown-item">
<input {idfy("item", item.id)} value={item.id} checked={checked}/>
<label for={idfy("item", item.id)}>
<%= item.name %>
</label>
</div>
<% end %>
The @shown_items
list changes with user interaction. The user types into an input box and only items that match the search string will appear, along with those that are already checked.
Under some circumstances which are reproducible in the browser but not in unit tests one of the <input>
tags remains. The html copied from the browser’s debugger looks like this:
<div class="shown-item">
<input id="item-3" type="checkbox" name="item[]" value="3" checked="">
<label for="item-3">
Item-3 name
</label>
</div>
<div class="shown-item">
<input id="item-1" type="checkbox" name="item[]" value="1"><input id="item-3" type="checkbox" name="item[]" value="3">
<label for="item-1">
Item-1 name
</label>
</div>
<div class="shown-item">
<input id="item-10" type="checkbox" name="item[]" value="10">
<label for="item-10">
Item-10 name
</label>
</div>
In the second div.shown-item
there is a input#item-3
which should not be there. It belongs in the first div.shown-item
where it also correctly shows up. A copy remains in the second.
Workaround / solution
What seems to work around this is to give every div.shown-item
a unique DOM id
. I changed the heex
code to
<%= for {item, checked} <- @shown_items do %>
<div class="shown-item" id={idfy("shown-item", item.id)}>
<input {idfy("item", item.id) value={item.id} checked={checked}/>
<label for={idfy("item", item.id)}>
<%= item.name %>
</label>
</div>
<% end %>
The idfy
function looks like
def idfy(string, id), do: string <> "-#{id}"