Why is LiveView sending the form with every delta?

I have the following setup, receiving the form in the deltas/diffs for some reason when I am only toggling favorites and pushing those updates on the socket.

I thought maybe the csrf is different, nope stays the same within the deltas and I am not touching the changeset during toggle favs.

table it set to phx-update=prepend

@impl true
  def handle_event("toggle-fav", %{"id" => id}, socket) do
    inventory = Stock.get_inventory!(id)
    {:ok, inventory} = Stock.update_inventory(inventory, %{"is_fav" => !inventory.is_fav})
    broadcast(inventory, :updated)
    {:noreply, socket}
  end


defp broadcast(inventory, event) do
    Phoenix.PubSub.broadcast(
      Ex.PubSub,
      "ssocks:uuid",
      {event, inventory}
    )
  end

  @impl true
  def handle_info({:updated, inventory}, socket) do
    socket =
      update(
        socket,
        :inventories,
        fn inventories -> [inventory | inventories] end
      )

    {:noreply, socket}
  end

It is not harmful but thinking maybe I am doing something wrong since I shouldn’t get the diff of something I didn’t change.

2 Likes

Can you show your form in your .leex file? It seems like liveview isn’t able to do full change tracking on your code, which indicates an improvement you can likely make in your view somewhere.

Also make sure you’re following the docs at Phoenix.HTML.Form — Phoenix.HTML v2.14.3 and Assigns and LiveEEx templates — Phoenix LiveView v0.15.4

I don’t think I am doing any of the pitfalls on those pages.

<%= f = form_for @changeset, "#",
  id: "add-inventory-form",
  phx_submit: "add",
  class: "notification is-success is-light box" %>
<%= label f, :unit %>
<%= select f, :unit, @units %>
<%= error_tag f, :unit %>
<%= label f, :name %>
<%= text_input f, :name %>
<%= error_tag f, :name %>
<%= label f, :description %>
<%= text_input f, :description %>
<%= error_tag f, :description %>
<%= submit "Check In", phx_disable_with: "Saving..." %>
</form>
<table class="table is-fullwidth is-hoverable is-striped">
  <thead>
    <tr>
      <th>Favourite</th>
      <th>Name</th>
      <th>Unit</th>
      <th>Description</th>
      <th>Last bought</th>
      <th></th>
    </tr>
  </thead>
  <tbody id="inventories" phx-update="prepend">
    <%= for inventory <- @inventories do %>
      <tr id="inventory-<%= inventory.id %>" <%= if inventory.__meta__.state == :deleted do %>class="is-hidden"<% end %>>
        <td>
          <%= link to: "#", phx_click: "toggle-fav", phx_value_id: inventory.id do %>
            <%= if inventory.is_fav do %>
              <span class="icon has-text-warning has-tooltip-arrow" data-tooltip="Remove from favorites">
                <i class="fas fa-star fa-lg"></i>
              </span>
            <% else %>
              <span class="icon has-tooltip-arrow" data-tooltip="Add to favorites">
                <i class="far fa-star fa-lg"></i>
              </span>
            <% end %>
          <% end %>
        </td>
        <td><%= inventory.name %></td>
        <td><%= inventory.unit %></td>
        <td><%= inventory.description %></td>
        <td><%= inventory.last_bought %></td>
        <td>
          <span>History</span>
          <span><%= live_patch "Edit", to: Routes.inventory_index_path(@socket, :edit, inventory) %></span>
          <span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: inventory.id, data: [confirm: "Are you sure?"] %></span>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

The interesting thing is it finishes right after the description text field, it is an optional field in the changeset.

As far as I know change tracking doesn’t work for form_for (at least at the moment). Most recent comment about this I can find is this one in Surface’s issue tracker by Marlus:

As far as I can remember, change tracking is disabled inside functions like form_for and any other helper function depending on content_tag.

If you read the LiveEEx Pitfalls section in the documentation, the use of form_for does fall under those scenarios for which change tracking won’t work.

When it comes to do/end blocks, change tracking is supported only on blocks given to Elixir’s basic constructs, such as if, case, for, and friends. If the do/end block is given to a library function or user function, such as content_tag, change tracking won’t work.

Due to the scope of variables, LiveView has to disable change tracking whenever variables are used in the template, with the exception of variables introduced by Elixir basic case, for, and other block constructs. Therefore, you must avoid code like this in your LiveEEx:

<% some_var = @x + @y %>
  • the form helpers (label, select .etc) depend on content_tag (search for ‘content_tag’ in this file)
  • f = form_for ...
1 Like

I completely glossed over the fact that the label, input depends on the content_tag, exclusion of submit button threw me off.

I solved this issue by making my form a live component.

handle_event("add", within the component and add the inventory to the db

then send the parent LV the new inventory

send(self(), {:new, inventory})

Now I only get the DELTAS I am interested in at add,edit,remove operations.

1 Like