Problem with Liveview sending diff with null values


I have a weird problem with Phoenix Liveview, after upgrading to the latest version. Liveview sends a diff with null values.

I have the following template :

#These lists are assigned in the socket
#rows = [%{id: 1, user_name: "user1" },%{id: 2, user_name: "user2" }]
#cols = [:id,:user_name]

def render(assigns) do
    <button phx-click="replace_users" phx-value="all" type="button" class="btn btn-link">Replace Users</button>


      <%= for row <- @rows do %>
          <%= for col <- @cols do %>
            <td><%= Map.get(row,col) %></td>
          <% end %>
      <% end %>

def handle_event("replace_users", _ , socket) do
  rows = [%{id: 2, user_name: "user2" },%{id: 3, user_name: "user3" }]
  {:noreply, assign(socket, rows: rows)}

The first render works fine but when I click on “Replace users”, the diff sent by Liveview contains

4: {response: {diff: {0: {d: [[null], [null]]}}}, status: "ok"}
response: {diff: {0: {d: [[null], [null]]}}}
diff: {0: {d: [[null], [null]]}}
0: {d: [[null], [null]]}
d: [[null], [null]]
0: [null]
1: [null]
status: "ok"

But it works fine if I replace

<%= for col <- @cols do %>
    <td><%= Map.get(row,col) %></td>
<% end %>


 <td><%= Map.get(row,:id) %></td>
 <td><%= Map.get(row,:user_name) %></td>

Am I missing something? Is it a Liveview bug? The first render is ok, but if I go through the list of the keys after the click on “Replace users”, it doesn’t work and Liveview sends a diff with null values.
Thanks for your help.

Can you show the output of IO.inspect(@cols) in the template?

Thanks for your answer, I get an error if I put IO.inspect(@cols).

# lists in Phoenix.HTML and templates may only contain integers representing bytes, binaries or other lists, got invalid entry: :id

But if I put

<%= for col <- @cols do %>
        <%= IO.inspect(col) %>

I get this when it mounts but it doesn’t show anything when I click on “Replace users”

[info] GET /live/index_change

[debug] Processing with Phoenix.LiveView.Plug.Elixir.DeviceLiveviewWeb.DeviceLive.IndexChange/2

Parameters: %{}

Pipelines: [:browser]





[info] Sent 200 in 1ms

[info] CONNECTED TO Phoenix.LiveView.Socket in 270µs

Transport: :websocket

Serializer: Phoenix.Socket.V2.JSONSerializer

Connect Info: .....
Parameters: .....





Thanks for your help.

Cool, so definitely seems like it night be a bug. Let’s get some basic info. Do the following and paste the results here:

mix deps|grep phoenix

Also, I would cd assets && rm -rf node_modules && npm install. Sometimes when you update live view, if the javascript has updated you gotta nuke the assets.

I did cd assets && rm -rf node_modules && npm install but I have still the same problem.

Here is the list of the deps:

* phoenix_pubsub 1.1.2 (Hex package) (mix)
  locked at 1.1.2 (phoenix_pubsub) 496c303b
* phoenix_html 2.13.3 (Hex package) (mix)
  locked at 2.13.3 (phoenix_html) 850e292f
* phoenix 1.4.11 (Hex package) (mix)
  locked at 1.4.11 (phoenix) d112c862
* phoenix_live_reload 1.2.1 (Hex package) (mix)
  locked at 1.2.1 (phoenix_live_reload) 274a4b07
* phoenix_live_view 0.4.1 ( (mix)
* phoenix_ecto 4.1.0 (Hex package) (mix)
  locked at 4.1.0 (phoenix_ecto) a044d075

Thanks again.

Cool. Your best step from here then is probably to file a bug report on phoenix live view. If you can provide a reproducible example I’m sure that’d expedite things.

Thanks to confirm that it seems to be a bug. I just reported this issue on phoenix liveview.
Thanks again.

If anybody else has the same problem that happens with nested loops (explanation on

I moved this code to a Livecomponent and it works fine :

          <%= for col <- @cols do %>
            <td><%= Map.get(row,col) %></td>
          <% end %>
      <% end %>

A temporary fix without using Livecomponent is to replace in Phoenix liveview engine.ex file:
defp classify_taint(:for, _), do: :never
defp classify_taint(:for, _), do: :live .
It’s not optimal since Liveview will send more diffs.