I think phx-update=append
would be appropriate for things like infinite scroll, if I don’t need to hold the state of previous results in memory, however my use case here is a form submit. I’m building up an ecto changeset with nested associations for the purposes of immediate feedback for validation, so I think it’s appropriate to have the entire changeset in memory.
Building this example was mostly straight forward because I can directly apply the knowledge (and implementation) from traditional static phoenix forms, except with liveview this form can tell me immediately when:
- there is a missing start or end time for period1
- when there is an overlap between period1 and period2
- when period2 starts before period1
- when there is any duplicate date
It’s pretty great.
I was just surprised that the memory usage was over 400k for even one workday.
Could it be that my form dropdowns that are timepickers are holding onto lots of memory? time_options() is a helper function that generates a list of tuples of strings (lots of concatenation to build them) and it is used in multiple places (4 times per workday) in the form. (I know Phoenix already comes with a time picker builder but it separates each component for HH, MM, etc, so it seemed to make the form too wide for me.)
If I remove the time pickers from the liveview form the initial process drops from 427k to 144k.
I remember from the talk that liveview separates the parts that change from the parts that don’t, but is there some special treatment that’s needed for looping over the nested associations in inputs_for
such that in the example below time_options
is cached for re-use?
<%= inputs_for f, :workdays, fn wd -> %>
<tr>
<td>
<%= date_select wd, :date, builder: fn b -> %>
<%= b.(:month, []) %> <%= b.(:day, []) %> <%= b.(:year, [options: 2018..2020]) %>
<% end %>
</td>
<td>
<%= select wd, :period1_start, time_options(), value: form_to_time(wd, :period1_start) %>
<%= error_tag wd, :period1_start %> -
<%= select wd, :period1_end, time_options(), value: form_to_time(wd, :period1_end) %>
<%= error_tag wd, :period1_end %>
</td>
<td>
<%= select wd, :period2_start, optional_time_options(), value: form_to_time(wd, :period2_start) %>
<%= error_tag wd, :period2_start %> -
<%= select wd, :period2_end, optional_time_options(), value: form_to_time(wd, :period2_end) %>
<%= error_tag wd, :period2_end %>
</td>
<td>
<%= Ecto.Changeset.get_field(wd.source, :hours) %>
</td>
<%= if @changeset.data.overnight_option_available do %>
<td><%= checkbox wd, :overnight %>
<%= error_tag wd, :overnight %></td>
<% end %>
<td><button type="button" phx-click="delete_workday" phx-value-workday_id="<%= wd.id %>" class="alert-danger">🗑</button></td>
</tr>
<% end %>