In the first part 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:
Then you may be tempted to do the following instead:
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:
Note: LiveView itself hints at, and counters this problem by excluding all other
assigns 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 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 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:
Then render it within the 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 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.
3. Growing LiveView Assigns Infinitely
As noted in the 'LiveView Assigns Manage State' part of the previous post, 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:
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:
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, 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, 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!