ericwikman
LiveView sending more data than expected
I’ve been trying to troubleshoot this issue, but I think I might have a fundamental misunderstanding of what is expected and maybe what I’m seeing is what is expected.
I assign a list of rows where each row is made up of a list of maps that represent the individual cells. Everything looks good in the UI, and updates as expected, but when I enableDebug on the liveSocket it looks like the entire interior of the table is being sent down the websocket when any change is made, not just the changed cell. It does properly breakup the static content with the dynamic content, but sends redundant data.
The reason I think the issue is my understanding is because I inspected the code and websocket communication on the LiveDashboard system on the Sockets page and basically see the same result as with my code. There is a table, and everytime the 15s update goes out, it looks like it is sending all the cells of the table, even the unchanged cells.
Here is the shortest version of my code. I dynamically adjust the number of columns and rows based on the screen size by using a phx-hook, here is the HTML:
<div id="page_resize" phx-hook="PageResize"></div>
here is the hook in app.js:
Hooks.PageResize = {
mounted() {
window.addEventListener("resize", (e) => {
this.pushEvent("page-resize", {
screenWidth: window.innerWidth,
screenHeight: window.innerHeight,
});
});
},
destroyed() {
// TODO: create this.pageResize in mounted so it can be destroyed
// window.removeEventListener("resize", this.pageResize);
},
};
and that event is handled by:
def handle_event(
"page-resize",
%{"screenWidth" => screenWidth, "screenHeight" => screenHeight},
socket
) do
socket =
assign(
socket,
screenWidth: screenWidth,
screenHeight: screenHeight,
rows: Table.Cell.list_rows(screenWidth, screenHeight)
)
{:noreply, socket}
end
which populates the “rows” with this list_rows function:
def list_rows(width, height) do
rows = Integer.floor_div(height - 2, 32)
cols = Integer.floor_div(width - 2, 132)
for r <- 1..rows do
for c <- 1..cols do
%{
row: r,
column: c,
string: "R#{r}C#{c}"
}
end
end
end
and finally in the render I have this code:
<table>
<%= for row <- @rows do %>
<tr>
<%= for cell <- row do %>
<td class="bg-gray-50 border-gray-200 border-2">
<div
tabindex="0"
class="font-mono w-32 overflow-hidden whitespace-nowrap pl-1 py-0.5 text-gray-700"
>
<%= cell.string %>
</div>
</td>
<% end %>
</tr>
<% end %>
</table>
When the window size changes enough to add a new row or column (or remove one), I would expect that only the new rows would come through the diff. But instead every cell comes through on the websocket update. Not just the innerText (like “R1C1”) but also the lengthy TD & DIV definition with all of the same classes and such.
Am I doing something wrong, or is this what I should expect?
I will say that the updates are super fast, but I’m doing local development and I’m not quite sure what it will be like for people with higher latency and lower bandwidth. So if I am doing something wrong to cause it to send so much data, I want to resolve it.
Elixir 1.11.2, Phoenix 1.5.7, OTP 23, LiveView 0.15.0
Most Liked
beepbeepbopbop
I had a very similar problem, a grid of cells that would represent a battleship game using LiveView. As you can imagine, if you were to click a cell, it would need to update the icon based on whether the selection was a hit or miss on the other player’s side. When I did it naively, I found that a single icon change would send the entire “row”, which confused me as I was under the impression that it would have just done the cell itself.
However, when you think about how diff tracking works, LiveView has to detect the changes that were made from the changed set of assigns. It has to evaluate those changes and send them down, which might be the reason why it’s sending so much.
I was told that one of the ways I could optimise this was to use LiveComponents and move the state tracking away from the LiveView itself. Initially I had something like this:
- Mount the live view entire grid with the grid state as the live view assign
- Render the grid as is
- User would click on the cell, which would have something like
handle_event("tile-clicked", %{"id" => tile_id}, socket) - Update the state on the assigns, which prompts the re-render
- Browser gets the entire row that was changed.
I had to change it a fair bit in order to reduce the count:
- Mount the live view entire grid with the grid state as a live view, but only use it as template
- Instead of rendering the cells as is, render them in a stateful component.
- When the cell is clicked, the component’s
handle_event/3callback is called - Send a message to the live view via
send(self(), {YourLiveViewComponent, tile_id, your_message}) - Have the parent view inspect the message and based off your logic, send the component an message with it’s updated assigns.
- This time the cell that changed only gets the patch.
You could have a layout like this:
defmodule YourLiveView do
use YourAppWeb, :live_view
# The rest omitted, but in your `leex` tempate should have render_component with the ID
def handle_info({:cell_update, component_id}, socket) do
# Check the state from the socket to get a new value
send_update(YourCellComponent, id: "component-#{component_id}", value: new_value)
{:noreply, socket}
end
end
defmodule YourCellComponent do
use YourAppWeb, :live_component
# The rest is omitted, but let's assume there's `:value` in the `assigns`.
def handle_event("cell-update", _params, socket) do
send(self(), {:cell_update, socket.assigns.id})
{:noreply, socket}
end
end
You can see a (poorly coded) example here: GitHub - nathancyam/battleship_ex · GitHub, but specifically these files:
- battleship_ex/lib/battleship_web/live/game_live.ex at master · nathancyam/battleship_ex · GitHub
- battleship_ex/lib/battleship_web/live/game_live/guess_result.ex at master · nathancyam/battleship_ex · GitHub
- battleship_ex/lib/battleship_web/live/tile_live.ex at master · nathancyam/battleship_ex · GitHub
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








