Hello everyone. I’m building an application streaming AI responses using LiveView. When checking websocket payload I noticed something unexpected.
This is a minimal replication example - process is receiving a lot of update events (coming as response from LLM is being streamed).
defmodule AppWeb.TestLive do
use AppWeb, :live_view
def handle_params(_params, _url, socket) do
socket =
socket
# faking a big data structure
|> assign(audience: %{test: Enum.to_list(1..1000)})
|> assign(selected_node: nil)
# faking frequent updates
Process.send_after(self(), "this is test", 30)
{:noreply, socket}
end
def handle_info(node, socket) do
# this is happening very frequently, ~50 times a second. I'm updating only selected_node
socket = assign(socket, :selected_node, node)
Process.send_after(self(), "this is test #{Enum.random(1..1000)}", 30)
{:noreply, socket}
end
def render(assigns) do
~H"""
<div :if={@audience}>
<script><%= Jason.encode!(@audience) |> raw %></script>
<div :if={@selected_node}>
<%= @selected_node %>
</div>
</div>
"""
end
end
My expectations is that payload sent to client will be tiny, only content of the
<div :if={@selected_node}>
<%= @selected_node %>
</div>
but for unknown reason also <%= Jason.encode!(@audience) |> raw %>
part is also sent as a payload. I’m not sure why, since it’s not in assigns.__changed__
LiveView should know nothing changed there?
Diff from DevTools
If I move :if={@audience}
to graph-data
, it works as expected
def render(assigns) do
~H"""
<div>
<script id="graph-data" :if={@audience}>
<%= Jason.encode!(@audience) |> raw %>
</script>
...
</div>
"""
end
In my case I was using @audience
assign in multiple places inside root node, that’s why I had it there. Working around this just to have smaller payload shouldn’t be needed? What do you think, are my expectations wrong and it just have to be that way?
I was thinking about creating issue on Github but posting here first seems less intrusive.