<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Karol Słuszniak</title>
    <description>The latest articles on DEV Community by Karol Słuszniak (@karolsluszniak).</description>
    <link>https://dev.to/karolsluszniak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F374574%2Fc574f19a-84b4-4804-910c-115b96172157.jpeg</url>
      <title>DEV Community: Karol Słuszniak</title>
      <link>https://dev.to/karolsluszniak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karolsluszniak"/>
    <language>en</language>
    <item>
      <title>LiveView Assigns: Three Common Pitfalls and Their Solutions</title>
      <dc:creator>Karol Słuszniak</dc:creator>
      <pubDate>Wed, 06 Jul 2022 12:56:01 +0000</pubDate>
      <link>https://dev.to/appsignal/liveview-assigns-three-common-pitfalls-and-their-solutions-4poa</link>
      <guid>https://dev.to/appsignal/liveview-assigns-three-common-pitfalls-and-their-solutions-4poa</guid>
      <description>&lt;p&gt;In the &lt;a href="https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html"&gt;first part&lt;/a&gt; of this two-part series, we examined LiveView assigns in detail — demystifying assigns, looking at some key concepts, and debugging.&lt;/p&gt;

&lt;p&gt;Now, we'll turn our attention to three common mistakes that you might make with assigns and how to avoid them.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Evaluating All LiveView Assigns
&lt;/h3&gt;

&lt;p&gt;As you pass assigns around to view helpers, and the complexity increases, you may need many assigns in some functions. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;user_note&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you may be tempted to do the following instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;user_note&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;assigns&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To solve this, stick to passing only the required assigns explicitly and collapse multiple arguments into a keyword list if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;user_note&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;theme:&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;locale:&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; LiveView itself hints at, and counters this problem by &lt;a href="https://hexdocs.pm/phoenix_live_view/0.15.0/Phoenix.LiveView.Socket.AssignsNotInSocket.html"&gt;excluding all other assigns&lt;/a&gt; from the widely used &lt;code&gt;@socket&lt;/code&gt; struct in which they’re generally stored. But it does not forbid you from reaching for the &lt;code&gt;assigns&lt;/code&gt; directly, opening a door to this issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Re-rendering Entire Lists
&lt;/h3&gt;

&lt;p&gt;Change tracking on nested data such as lists is a complex problem, regardless of the framework. LiveView goes the extra mile to represent &lt;code&gt;for&lt;/code&gt; loops via a &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Comprehension.html"&gt;dedicated struct&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;Our generated live resource in the 'Caveman Debugging in LiveView' section of the &lt;a href="https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html"&gt;first part of this series&lt;/a&gt; re-evaluated every table row and cell regardless of what we did with the &lt;code&gt;@notes&lt;/code&gt; assign.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;NotesLive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Index&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;NoteRow&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_component&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;tr id={"&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="c1"&gt;#{@note.id}"}&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;@note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;@note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;!&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="no"&gt;ACTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"""
  end
end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then render it within the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- TABLE HEADER (CUT) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;notes&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;live_component&lt;/span&gt; &lt;span class="na"&gt;module=&lt;/span&gt;&lt;span class="s"&gt;{__MODULE__.NoteRow}&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;{"note-row-#{note.id}"}&lt;/span&gt; &lt;span class="na"&gt;note=&lt;/span&gt;&lt;span class="s"&gt;{note}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you may once again create, edit, and delete some notes for the following highly desired behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When &lt;em&gt;creating&lt;/em&gt;, only the newly added row gets updated.&lt;/li&gt;
&lt;li&gt;When &lt;em&gt;editing&lt;/em&gt;, only the cells for changed fields get updated.&lt;/li&gt;
&lt;li&gt;When &lt;em&gt;deleting&lt;/em&gt;, no other rows get updated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: 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.&lt;/p&gt;

&lt;p&gt;The excellent article &lt;a href="https://thepugautomatic.com/2020/07/optimising-data-over-the-wire-in-phoenix-liveview/"&gt;&lt;em&gt;Optimising data-over-the-wire in Phoenix LiveView&lt;/em&gt;&lt;/a&gt; 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 — &lt;a href="https://github.com/phoenixframework/phoenix_live_view/blob/master/CHANGELOG.md#0173-2021-10-28"&gt;see the Phoenix LiveView changelog&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Growing LiveView Assigns Infinitely
&lt;/h3&gt;

&lt;p&gt;As noted in the 'LiveView Assigns Manage State' part of the &lt;a href="https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html"&gt;previous post&lt;/a&gt;, 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;NotesLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div id="&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
      &amp;lt;%= for note &amp;lt;- @notes do %&amp;gt;
        &amp;lt;div id={"&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="c1"&gt;#{note.id}"}&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;!&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="no"&gt;CUT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;phx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"load_more"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s2"&gt;"""
  end
  def handle_event("&lt;/span&gt;&lt;span class="n"&gt;load_more&lt;/span&gt;&lt;span class="s2"&gt;", _, 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
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The solution is to mark that assign as temporary, switch to the &lt;code&gt;append&lt;/code&gt; update model for the note listing, and only assign new items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;NotesLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;temporary_assigns:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;notes:&lt;/span&gt; &lt;span class="p"&gt;[]]}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div id="&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="s2"&gt;" phx-update="&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
      &amp;lt;!-- CUT (notes loop) --&amp;gt;
    &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"load_more"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;next_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_page&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;more_notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_notes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;page:&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;notes:&lt;/span&gt; &lt;span class="n"&gt;more_notes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;last_page:&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;
    &lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the notes list will still accumulate newly loaded records while keeping memory usage under control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://blog.appsignal.com/2022/06/14/a-guide-to-phoenix-liveview-assigns.html"&gt;first part of this series&lt;/a&gt;, we spent some time diving into LiveView assigns. First, we demystified assigns, before embarking on some caveman debugging and socket inspection.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Happy assigning!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A note from AppSignal&lt;/em&gt;: We're very excited to have recently released &lt;a href="https://blog.appsignal.com/2022/06/20/appsignal-for-phoenix-2-1-automatic-liveview-instrumentation.html"&gt;AppSignal for Phoenix 2.1&lt;/a&gt;, which adds automatic instrumentation for LiveView through Telemetry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>A Guide to Phoenix LiveView Assigns</title>
      <dc:creator>Karol Słuszniak</dc:creator>
      <pubDate>Tue, 21 Jun 2022 11:35:21 +0000</pubDate>
      <link>https://dev.to/appsignal/a-guide-to-phoenix-liveview-assigns-4eek</link>
      <guid>https://dev.to/appsignal/a-guide-to-phoenix-liveview-assigns-4eek</guid>
      <description>&lt;p&gt;Phoenix LiveView lets you develop full-stack apps with client-side interactions while mostly avoiding cross-stack hassle. Assigns, managed by the LiveView socket, are a core tool for making that happen — allowing you to store, present, and update data effortlessly and efficiently. But as they do so much, assigns come with their own complexities and may backfire if misused.&lt;/p&gt;

&lt;p&gt;Over the last three years, I've had the pleasure to work on multiple LiveView apps within multiple teams, so I know first-hand that assigns can feel like magic to many developers (myself included).&lt;/p&gt;

&lt;p&gt;In this article, we're going to demystify LiveView assigns. We'll describe what LiveView assigns actually are in relation to popular frameworks, find out how assigns work in practice, and highlight some of the traps that one may easily fall into.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: Throughout this post, we're going to use &lt;em&gt;assigns&lt;/em&gt; to refer to LiveView assigns — as opposed to EEx assigns, Plug assigns, or Phoenix Socket assigns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demystifying Phoenix LiveView Assigns
&lt;/h2&gt;

&lt;p&gt;Let's start with an overview of what assigns actually are. Here's a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;StatsLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Notes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;note_count:&lt;/span&gt; &lt;span class="no"&gt;Notes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_note_count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="ss"&gt;user_count:&lt;/span&gt; &lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_user_count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    Note count: &amp;lt;%= @note_count %&amp;gt;
    User count: &amp;lt;%= @user_count %&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above snippet shows how &lt;code&gt;@note_count&lt;/code&gt; and &lt;code&gt;@user_count&lt;/code&gt; are assigned initial values and then rendered as dynamic fragments among the static HTML that surrounds them. That's the concept of LiveView assigns in a nutshell.&lt;/p&gt;

&lt;p&gt;Now let's take a deeper look, pointing out some similarities and differences between assigns and mainstream front-end development.&lt;/p&gt;

&lt;p&gt;Let's get going!&lt;/p&gt;

&lt;h3&gt;
  
  
  LiveView Assigns Provide Dynamic Input
&lt;/h3&gt;

&lt;p&gt;Assigns in LiveView build on top of &lt;a href="https://hexdocs.pm/eex/EEx.html#module-macros"&gt;those used by EEx&lt;/a&gt; as well as &lt;a href="https://hexdocs.pm/plug/Plug.Conn.html#module-connection-fields"&gt;from "traditional" Phoenix&lt;/a&gt;. They are just input variables — placeholders for dynamic data provided from the outside.&lt;/p&gt;

&lt;p&gt;Run the following code from &lt;code&gt;iex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EEx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eval_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello, &amp;lt;%= @name %&amp;gt;!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="s2"&gt;"Hello, John!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can easily compare assigns to props from React, Vue, or any other web component library. This is especially true for stateless components that have recently switched to simple function syntax, like functional components in React. Consider this simple component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;MyComponents&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;my_button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;click:&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;button class="&lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="s2"&gt;" phx-click={@click}&amp;gt;
      &amp;lt;%= @text %&amp;gt;
    &amp;lt;/button&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can surely imagine yourself rewriting it in any web component library with a very similar code structure and volume. For the record, here's how similar it'd be in React:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;click&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some-class&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component uses assigned input wherever it needs to, while the caller of &lt;code&gt;my_button&lt;/code&gt; provides it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;my_button&lt;/span&gt; &lt;span class="na"&gt;text=&lt;/span&gt;&lt;span class="s"&gt;"Click me!"&lt;/span&gt; &lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"some_event"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy enough, and no room for dilemmas. But that's just the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  LiveView Assigns Manage State
&lt;/h3&gt;

&lt;p&gt;For live views and live components, assigns grow from being a dumb input into a long-lived state. As such, they derive from &lt;a href="https://hexdocs.pm/phoenix/channels.html#overview"&gt;Phoenix Channel assigns&lt;/a&gt;, and shift from props into state in the React nomenclature.&lt;/p&gt;

&lt;p&gt;Views and components may opt to update their assigns from the inside to evolve their state. This is nothing fancy on its own, so — just like with props — we can easily find an equivalent in any web component framework.&lt;/p&gt;

&lt;p&gt;What is fancy, however, is that in LiveView this happens on the server — taking advantage of Erlang processes to deliver low latency at scale.&lt;/p&gt;

&lt;p&gt;To understand why this is great, let's take a look at the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserSubscriptionLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pick_subscription"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# encrypted session&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;assigns:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;current_user:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;

    &lt;span class="c1"&gt;# CSRF-protected user input&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"plan"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

    &lt;span class="c1"&gt;# ACID-safe database&lt;/span&gt;
    &lt;span class="n"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Subscriptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# non-public, secure APIs&lt;/span&gt;
    &lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;charge_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# server-only background jobs&lt;/span&gt;
    &lt;span class="no"&gt;Mailing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_subscription_created_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subscription:&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without any effort, we mix data from the session, user input, databases, APIs, background jobs, and PubSubs — in one place, untampered, and server-side rendered. Presenting the results is just as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;unless&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;subscription&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Pick your plan:

  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;phx-click=&lt;/span&gt;&lt;span class="s"&gt;"pick_subscription"&lt;/span&gt; &lt;span class="na"&gt;phx-value-plan=&lt;/span&gt;&lt;span class="s"&gt;"basic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Basic ($4.99)&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;phx-click=&lt;/span&gt;&lt;span class="s"&gt;"pick_subscription"&lt;/span&gt; &lt;span class="na"&gt;phx-value-plan=&lt;/span&gt;&lt;span class="s"&gt;"premium"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Premium ($19.99)&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;else&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  You're currently subscribed to &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;format_plan&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;subscription.plan&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; plan.

  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;subscription.last_payment&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    You were last charged on &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;DateTime.to_date&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;subscription.last_payment.inserted_at&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine all the moving parts that it would take to produce and fully test the client-side equivalent of our &lt;code&gt;@subscription&lt;/code&gt; assign. This is perhaps the biggest highlight of LiveView, stealing the spotlight from API layers, state managers, sagas, and browser-based test suites.&lt;/p&gt;

&lt;p&gt;At the same time, state management is undoubtedly a challenging task, and keeping memory usage under control is one of our main concerns. This is especially true considering we're on the server, and all connected users will claim the memory needed to hold the assigns during their entire session.&lt;/p&gt;

&lt;p&gt;We'll examine a specific use case along with its solution in a moment.&lt;/p&gt;

&lt;h3&gt;
  
  
  LiveView Assigns Fuel Change Tracking
&lt;/h3&gt;

&lt;p&gt;If you think that assigns are "just" props and state, and the show is over, brace yourself.&lt;/p&gt;

&lt;p&gt;You see, if you update a single React prop or piece of React state, the whole component will re-render. But LiveView assigns are different. The reason for this is HEEx — a templating engine that splits your template into static and dynamic parts, and then only re-evaluates dynamic parts to involve the changed assigns.&lt;/p&gt;

&lt;p&gt;Since this happens on the server, it's only the actual changes that will ever take up bandwidth, saving you the effort needed to slim down JSON payloads in classic SPAs, e.g., by designing case-specific or flexible APIs. That's some cool out-of-the-box optimization.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; This feature is known by different names, including &lt;strong&gt;change tracking&lt;/strong&gt;, &lt;strong&gt;reactivity&lt;/strong&gt;, &lt;strong&gt;memoization&lt;/strong&gt;, or &lt;strong&gt;observability&lt;/strong&gt;. The approach towards it varies across JS frameworks — check out the above keywords regarding Angular, Ember, Knockout, or Svelte.&lt;/p&gt;

&lt;p&gt;In React, we can achieve similar behavior in a more explicit way (so it'll serve as a great visual exercise) with &lt;a href="https://reactjs.org/docs/hooks-reference.html#usememo"&gt;&lt;code&gt;useMemo&lt;/code&gt;&lt;/a&gt;. Consider the following live component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;UserHoroscope&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;MyAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_component&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div&amp;gt;
      &amp;lt;strong&amp;gt;Horoscope for {format_full_name(@first_name, @last_name)} (born on {format_date(@birthday)}):&amp;lt;/strong&amp;gt;
      {generate_horoscope(@birthday)}
    &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Out of the box, it will only call &lt;code&gt;format_date&lt;/code&gt; or &lt;code&gt;get_horoscope&lt;/code&gt; when &lt;code&gt;@birthday&lt;/code&gt; changes, but not when either &lt;code&gt;@first_name&lt;/code&gt; or &lt;code&gt;@last_name&lt;/code&gt; do (which may be extra useful if our generator that powers the horoscope becomes complex and the user name changes). The same goes for the other &lt;code&gt;&amp;lt;%= ... %&amp;gt;&lt;/code&gt; snippets.&lt;/p&gt;

&lt;p&gt;With React, we start off with the following naive component that runs all helper functions, regardless of changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;UserHoroscope&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birthday&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Horoscope&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formatFullName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;born&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;)}):&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/strong&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getHoroscope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we'd extend it into the following shape with &lt;code&gt;useMemo&lt;/code&gt; so that each helper is called only when its dependencies change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;UserHoroscope&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birthday&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;formatFullName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedBirthday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;horoscope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;generateHoroscope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;strong&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Horoscope&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;born&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formattedBirthday&lt;/span&gt;&lt;span class="p"&gt;}):&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/strong&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;horoscope&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; We're not saying React is the worst here. React focuses on optimizing historically slow DOM patching while assuming that modern JS is fast enough to re-evaluate typical rendering logic (ideal for a client-side library). LiveView goes the extra mile to save on server resources and bandwidth without sacrificing the dev experience — which works well for a compiler-backed server-side library.&lt;/p&gt;

&lt;p&gt;Overall, change tracking is a powerful feature that does some really useful stuff under the hood while keeping our code clean. But it may also raise some ambiguity and doubts as far as assigns are concerned, and, if used without care, change tracking can definitely backfire performance-wise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking LiveView Assigns for a Spin
&lt;/h2&gt;

&lt;p&gt;Now that we know multiple concepts are mixed in with assigns, we see how they may actually not be as simple as they look in the code.&lt;/p&gt;

&lt;p&gt;LiveView docs have a dedicated &lt;a href="https://hexdocs.pm/phoenix_live_view/assigns-eex.html"&gt;assigns and HEEx guide&lt;/a&gt;, so that's the first place you should look for answers and pointers. The module page for the &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Engine.html"&gt;template engine&lt;/a&gt; gives some extra insight.&lt;/p&gt;

&lt;p&gt;But, sooner or later (and I'd put my bets on sooner considering the early stage of LiveView), that isn't enough. Let's look at how to approach LiveView to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Play with it and boost your confidence&lt;/li&gt;
&lt;li&gt;Debug and fix suspicious behavior in a real app&lt;/li&gt;
&lt;li&gt;Check for optimizations or regressions in new LiveView releases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Caveman Debugging in LiveView
&lt;/h3&gt;

&lt;p&gt;The first technique that you may want to employ involves returning to the very roots of debugging. You'll use a battle-proven, highly sophisticated technique that's been hiding in the darkest corners of the internet under numerous inspiring names like &lt;a href="https://stackoverflow.com/a/189639"&gt;'caveman debugging'&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The idea is to rely on the fact that HEEx only re-evaluates a subset of code blocks and to output a timestamp as close as possible to the place you want to inspect in the template.&lt;/p&gt;

&lt;p&gt;To try it out, generate a sample live resource for testing in your Phoenix 1.6 app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.gen.live Notes Note notes name:string content:text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the printed instructions and navigate to &lt;code&gt;/notes&lt;/code&gt;. Then add the following &lt;code&gt;inspect/1&lt;/code&gt; calls to your &lt;code&gt;index.html.heex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;inspect&lt;/span&gt;&lt;span class="err"&gt;({&lt;/span&gt;&lt;span class="na"&gt;Time.utc_now&lt;/span&gt;&lt;span class="err"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;:above_notes&lt;/span&gt;&lt;span class="err"&gt;})&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- CUT (table header) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;notes&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;{"note-#{note.id}"}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;inspect&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;Time.utc_now&lt;/span&gt;&lt;span class="err"&gt;())&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;inspect&lt;/span&gt;&lt;span class="err"&gt;({&lt;/span&gt;&lt;span class="na"&gt;Time.utc_now&lt;/span&gt;&lt;span class="err"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;note.name&lt;/span&gt;&lt;span class="err"&gt;})&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;inspect&lt;/span&gt;&lt;span class="err"&gt;({&lt;/span&gt;&lt;span class="na"&gt;Time.utc_now&lt;/span&gt;&lt;span class="err"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;note.content&lt;/span&gt;&lt;span class="err"&gt;})&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- CUT (actions cell) --&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now try adding and removing some notes. In the default generated live resource, you should notice that the &lt;code&gt;:above_notes&lt;/code&gt; timestamp updates together with all the per-row ones when creating or editing notes, but not when deleting them.&lt;/p&gt;

&lt;p&gt;This hints that something different is happening with the live view and its assigns for the delete case. And rightfully so. When you look at the code, &lt;code&gt;FormComponent&lt;/code&gt; uses &lt;code&gt;push_redirect/2&lt;/code&gt; upon a save, causing the whole index to reload, while &lt;code&gt;Index&lt;/code&gt; uses &lt;code&gt;assign/2,3&lt;/code&gt; upon deletion to only update that specific assign.&lt;/p&gt;

&lt;p&gt;If you want to avoid the reload, it's as simple as replacing &lt;code&gt;push_redirect/2&lt;/code&gt; in &lt;code&gt;FormComponent&lt;/code&gt; with &lt;code&gt;push_patch/2&lt;/code&gt;, plus reloading the &lt;code&gt;@notes&lt;/code&gt; assign in &lt;code&gt;Index&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;apply_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;socket&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Listing Notes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:notes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_notes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà: your first performance tweak!&lt;/p&gt;

&lt;p&gt;You may also have noticed that all per-row timestamps re-render when you create, edit, and delete notes — regardless of whether a specific note is affected or not. We'll get back to this later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Socket Inspection
&lt;/h3&gt;

&lt;p&gt;Sometimes, it's useful to see what LiveView pushes down the wire. This isn't so easy to visually tie to specific places on the page. But it allows us to inspect what is getting updated, and when, without template changes, and — this is especially useful — to see payload size and structure.&lt;/p&gt;

&lt;p&gt;In order to inspect a socket:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit your live view in Chrome.&lt;/li&gt;
&lt;li&gt;Open Developer Tools.&lt;/li&gt;
&lt;li&gt;Switch to the &lt;strong&gt;Network&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Optionally filter by the &lt;strong&gt;WS&lt;/strong&gt; type.&lt;/li&gt;
&lt;li&gt;Choose the &lt;code&gt;websocket?_csrf_token=...&lt;/code&gt; name.&lt;/li&gt;
&lt;li&gt;Switch to the &lt;strong&gt;Messages&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Choose the message that you're interested in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's how it might look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EklboBIg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-06/live-websocket-inspect-devtools.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EklboBIg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-06/live-websocket-inspect-devtools.png" alt="Chrome devtools showing the LiveView rendering payload for two notes." width="745" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When working with two notes, the highlighted piece of inbound payload is responsible for filling the listing. This confirms that the entire listing is re-rendered, regardless of operation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; You may find it more handy to ask LiveView's JS client to log updates to the console, by calling &lt;code&gt;liveSocket.enableDebug()&lt;/code&gt; in it.&lt;/p&gt;

&lt;p&gt;In the end, having insight into the raw payload can lead you to some case-specific optimizations, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restructuring the assigns (e.g., by breaking apart large or nested ones)&lt;/li&gt;
&lt;li&gt;Using live components (to ensure some assigns are tracked separately)&lt;/li&gt;
&lt;li&gt;Sending some data to JavaScript hooks as binary (by switching to &lt;a href="https://hexdocs.pm/phoenix/channels.html"&gt;Phoenix.Channel&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debugging in Production with AppSignal
&lt;/h2&gt;

&lt;p&gt;The caveman technique is great for a single developer working on an application that hasn't been pushed to production. However, if you have an app in production with live users, you may want to take a look at &lt;a href="https://appsignal.com/"&gt;AppSignal&lt;/a&gt; for monitoring your application performance and checking for errors in production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.appsignal.com/elixir/installation/#installing-the-package"&gt;Adding AppSignal to an existing application&lt;/a&gt; takes a few seconds, and you can track LiveView errors from there.&lt;/p&gt;

&lt;p&gt;Here's an example of the AppSignal dashboard collecting data from a sample application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dl9lBtEJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-04/appsignal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dl9lBtEJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.appsignal.com/images/blog/2022-04/appsignal.png" alt="AppSignal Screenshot" width="880" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;In this post, we started off by demystifying LiveView assigns, before touching on some cross-over between assigns and mainstream front-end development. Finally, we explored caveman debugging in LiveView and socket inspection.&lt;/p&gt;

&lt;p&gt;LiveView combines a lot of functionality in assigns, including some that's just as clever and useful as it is implicit and complex.&lt;/p&gt;

&lt;p&gt;The key is to get a grasp on the key concepts, which are all relatable to what we can find in client-side frameworks. And, considering the framework's young age, to have a fallback solution for when existing resources can't help — a way to dig into it on your own.&lt;/p&gt;

&lt;p&gt;Next up in this two-part series, we'll look at some common pitfalls when it comes to LiveView assigns.&lt;/p&gt;

&lt;p&gt;Until then, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
  </channel>
</rss>
