LiveView does not perform diffing inside lists. Therefore, if you change one item in the list, from LiveView’s view the whole list is changed and therefore the for
is evaluated again. The recommended approach to handle this is streams, which also have the advantage of not taking up memory on the server.
There is another way to do it, if you need to keep state on the server: LiveComponents. If you refactor the list items to be rendered in their own LiveComponents, the diff will be minimized as well:
defmodule TestLiveViewUpdatesWeb.ListComponent do
use TestLiveViewUpdatesWeb, :live_component
def render(assigns) do
~H"""
<li><%= @element %></li>
"""
end
end
defmodule TestLiveViewUpdatesWeb.HomeLive do
use TestLiveViewUpdatesWeb, :live_view
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(
elements: ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
)}
end
def render(assigns) do
~H"""
<div>
<ul>
<.live_component id={element} module={TestLiveViewUpdatesWeb.ListComponent} :for={element <- @elements} element={element} />
</ul>
<br /><br />
<button phx-click="change_four">Change Four</button>
</div>
"""
end
def handle_event("change_four", _params, socket) do
elements = List.replace_at(socket.assigns.elements, 3, "#{:rand.uniform(1000)}")
{:noreply, assign(socket, elements: elements)}
end
end