LiveView Assigns: Three Common Pitfalls and Their Solutions
Tags: elixir
Published: 2022-07-06 12:56:01

In the [first part](https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html) of this two-part series, we examined LiveView assigns in detail — demystifying assigns, looking at some key concepts, and debugging.

Now, we'll turn our attention to three common mistakes that you might make with assigns and how to avoid them.

Let's get started!

### 1. Evaluating All LiveView Assigns

As you pass assigns around to view helpers, and the complexity increases, you may need many assigns in some functions. For example:

```html
<%= user_note(@user, @note, @theme, @locale) %>
```

Then you may be tempted to do the following instead:

```html
<%= user_note(assigns) %>
```

The problem with this simplification is that it’ll completely ruin change tracking and, as a result, changing any assign will trigger an update.

To solve this, stick to passing only the required assigns explicitly and collapse multiple arguments into a keyword list if needed:

```html
<%= user_note(@user, @note, theme: @theme, locale: @locale) %>
```

_Note:_ LiveView itself hints at, and counters this problem by [excluding all other assigns](https://hexdocs.pm/phoenix_live_view/0.15.0/Phoenix.LiveView.Socket.AssignsNotInSocket.html) from the widely used `@socket` struct in which they’re generally stored. But it does not forbid you from reaching for the `assigns` directly, opening a door to this issue.

### 2. Re-rendering Entire Lists

Change tracking on nested data such as lists is a complex problem, regardless of the framework. LiveView goes the extra mile to represent `for` loops via a [dedicated struct](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Comprehension.html) so that static parts are only sent once. But when it comes to assigns, it tracks all that appear in such loops as a whole.

Our generated live resource in the 'Caveman Debugging in LiveView' section of the [first part of this series](https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html) re-evaluated every table row and cell regardless of what we did with the `@notes` assign.

There’s a solution though. We can establish a separate tracking context using stateful live components. So let’s try it out and create a component for each note:

```elixir
defmodule MyAppWeb.NotesLive.Index.NoteRow do
  use MyAppWeb, :live_component
  def render(assigns) do
    ~H"""
    <tr id={"note-#{@note.id}"}>
      <td><%= inspect(Time.utc_now()) %></td>
      <td><%= inspect({Time.utc_now(), @note.name}) %></td>
      <td><%= inspect({Time.utc_now(), @note.content}) %></td>
      <!-- ACTIONS (CUT) -->
    </tr>
    """
  end
end
```

Then render it within the table:

```html
<table>
  <!-- TABLE HEADER (CUT) -->
  <tbody id="notes">
    <%= for note <- @notes do %> <.live_component module={__MODULE__.NoteRow}
    id={"note-row-#{note.id}"} note={note} /> <% end %>
  </tbody>
</table>
```

Now you may once again create, edit, and delete some notes for the following highly desired behavior:

- When _creating_, only the newly added row gets updated.
- When _editing_, only the cells for changed fields get updated.
- When _deleting_, no other rows get updated.

_Note_: You should not have to worry about memory usage caused by copying assigns to live components. They reside on the same process as the parent view, and so should share immutable data.

The excellent article [_Optimising data-over-the-wire in Phoenix LiveView_](https://thepugautomatic.com/2020/07/optimising-data-over-the-wire-in-phoenix-liveview/) compared Websocket payload for the naive loop vs. the component-based version. Note, however, that it may not reflect the current state of affairs as these things are rapidly evolving — [see the Phoenix LiveView changelog](https://github.com/phoenixframework/phoenix_live_view/blob/master/CHANGELOG.md#0173-2021-10-28).

### 3. Growing LiveView Assigns Infinitely

As noted in the 'LiveView Assigns Manage State' part of the [previous post](https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html), the server-side nature of LiveView places extra responsibilities on memory management. For that reason, you can't afford to grow lists infinitely, e.g., when paginating, as shown below:

```elixir
defmodule MyAppWeb.NotesLive do
  def render(assigns) do
    ~H"""
    <div id="notes">
      <%= for note <- @notes do %>
        <div id={"note-#{note.id}"}>
          <!-- CUT (note) -->
        </div>
      <% end %>
    </div>
    <button phx-click="load_more">Load more</button>
    """
  end
  def handle_event("load_more", _, socket) do
    next_page = socket.assigns.last_page + 1
    more_notes = Notes.list_notes(page: next_page)
    {:noreply, assign(socket,
      notes: socket.assigns.notes ++ more_notes,
      last_page: next_page
    )}
  end
end
```

Here, the memory usage will grow linearly, not just with the number of users (which is a problem we definitely don't want to have) but also with the total number of notes.

The solution is to mark that assign as temporary, switch to the `append` update model for the note listing, and only assign new items:

```elixir
defmodule MyAppWeb.NotesLive do
  def mount(params, session, socket) do
    # ...
    {:ok, socket, temporary_assigns: [notes: []]}
  end
  def render(assigns) do
    ~H"""
    <div id="notes" phx-update="append">
      <!-- CUT (notes loop) -->
    </div>
    """
  end
  def handle_event("load_more", _, socket) do
    next_page = socket.assigns.last_page + 1
    more_notes = Notes.list_notes(page: next_page)
    {:noreply, assign(socket,
      notes: more_notes,
      last_page: next_page
    )}
  end
end
```

Now the notes list will still accumulate newly loaded records while keeping memory usage under control.

## Wrap Up

In the [first part of this series](https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html), we spent some time diving into LiveView assigns. First, we demystified assigns, before embarking on some caveman debugging and socket inspection.

In this article, we examined three common pitfalls — evaluating all assigns, re-rendering entire lists, and growing assigns infinitely — and their solutions.

I hope that this series has given you an idea of some trade-offs that come with LiveView assigns, alongside ways to get the most out of the wonderful technology that Phoenix LiveView definitely is.

Happy assigning!

_A note from AppSignal_: We're very excited to have recently released [AppSignal for Phoenix 2.1](https://blog.appsignal.com/2022/06/20/appsignal-for-phoenix-2-1-automatic-liveview-instrumentation.html), which adds automatic instrumentation for LiveView through Telemetry.

**P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, [subscribe to our Elixir Alchemy newsletter and never miss a single post](/elixir-alchemy)!**