Optimizing LiveView diff for comprehensions


I am playing with LiveView and I find the diff mechanism to be not very efficient with comprehensions, or I am doing something wrong.

Let’s say I have a 100x100 table where user can click a cell to select it.

It is represented just by list of lists and it is passed as assign to the live view template:

  @impl true
  def mount(_params, _session, socket) do
    table =
      for i <- 1..100 do
        for j <- 1..100 do

      |> assign(:selected_cell, nil)
      |> assign(:table, table)

And rendered using comprehensions:

<%= for row <- @table do %>
    <%= for cell <- row do %>
      <td phx-click="cell_clicked" {"phx-value-cell": cell]}>
        <%= if @selected_cell == cell do %>
          <b><%= cell %></b>
        <% else %>
          <%= cell %>
        <% end %>
    <% end %>
<% end %>

Once the user clicks the cell, it marks the cell as selected, causing re-render:

  @impl true
  def handle_event("cell_clicked", %{"cell" => cell}, socket) do
    {:noreply, assign(socket, :selected_cell, cell)}

The problem is that the size of diff being sent over web socket is 600 KB. It seems that it contains updated contents of the whole table. I’ve read the docs on how comprehensions are handled but the approach explained in the docs seems to be very limited. IMO it should be smart enough to update just the particular cell.

Is it how it is supposed to work or am I doing something wrong? Any workaround to avoid re-sending the whole table? Or maybe can you give any guidance on how the diff mechanism can be improved if this is on purpose?

Comprehensions are not optimized by default. If anything in the source changes everything is updated. You can however optimize things by making the inner parts of the conprehension stateful live components as much as possible. They have their own diff context and therefore allow for granular diff tracking even in the coarse context of comprehensions.

They are a bit optimized out of the box. The inner block on the comprehension is split into static and dynamic parts, so it’s not sending the full rendered html for each element (static parts are sent only once).

But depending on the size of the list, the payload can still get very big.

I have wrapped <td> into LiveComponent. When it is clicked, it handles the event on the component level, sends the message to the parent view, which stores which cell is selected and then passes this down to the component in the template.

The updates went down to 70 KB. Still too large.

I went further and wrapped <tr> into component. This does not reduce the size of the diff.

I created an issue covering this problem: Diff mechanism should send changes for tags that have really changed in comprehensions · Issue #2187 · phoenixframework/phoenix_live_view · GitHub