I ran into a showstopper problem with v0.10.0 when upgrading from v0.4. So far, I have been unable to re-create it with a simple example. I will try to characterize it as clearly as I can:
I have a LiveView within a LiveView. The render/1 function has a for loop:
~L"""
...
<% Logger.warn("RENDERING for devices #{inspect @device_ids}") %>
<%= for device_id <- @device_ids do %>
<% Logger.warn("RENDERING FOR DEVICE #{device_id}") %>
...
"""
I defined a number of handle_info and handle_event functions that update the socket assigns.
For example,
def handle_event("close_health_details", %{"device_id" => device_id}, socket) do
updated_shown = List.delete(socket.assigns.showing_health_of, device_id)
{:noreply, assign(socket, :showing_health_of, updated_shown)}
end
def handle_info({:devices_health, devices_health}, socket) do
{:noreply, assign(socket, :devices_health, devices_health)}
end
When any of my handle_info functions is executed, the socket assigns are changed but there is no change to content shown in the browser for anything within the <%= for ... %> loop even though there should be. Note that everything works as expected with LiveView 0.4.
I verified that the logging statement inside the for loop is not executed after my handle_info functions have altered socket assigns. It is however executed after most handle_events run, but not all (the one shown above is one such).
Interestingly, if I replace <%= for ... %> with <% for ... %>, then the content rendered within the for loop is not displayed as expected BUT the logging statement within the loop is now executed everytime a handle_info or handle_event is executed that changes socket assigns.
The render code within the <%= for ...%> loop is always run when socket assigns are changed by handle_info and handle_event functions, as it does in v0.4
Please let me know how I can help target the source of the problem.
Where is the code that sets @device_ids? (your sample handler only sets :showing_health_of)
Asking because I had similar problems when I upgraded, and it was because my render function wasn't pure.
Can you please provide a sample app that reproduces the error? Then I can take a further look into the root cause. Thank you!
Where is the code that sets
@device_ids? (your sample handler only sets:showing_health_of)Asking because I had similar problems when I upgraded, and it was because my render function wasn't pure.
The list @device_ids is mostly static. It is not empty when the <%= for device_id <- @device_ids do %> loop fails (silently) to execute. The log then looks like this:
[warn] RENDERING for devices [1, 6, 7, 8, 9, 10, 26]
[warn] RENDERING for devices [1, 6, 7, 8, 9, 10, 26]
When the for loop does execute, the log looks like
[warn] RENDERING for devices [1, 6, 7, 8, 9, 10, 26]
[warn] RENDERING FOR DEVICE 1
...
[warn] RENDERING FOR DEVICE 6
...
I only showed two example handle_* functions that do not trigger execution of the <%= for ... %> loop. They are trivial. They change assign properties that do affect what gets rendered within the for-loop, when it is executed.
Could you elaborate about your similar problems? Maybe it will give me a clue.
Basically I had a list of IDs in an assign, and loaded the items from DB in the render function. But since LiveView requires rendering functions / templates to be pure (same input always gives the same output), it saw that the IDs hadn't changed, and skipped rendering the for loop, because it figured there wouldn't be any diff. That didn't work in my case, because the items had actually been modified in database by the event handler...
The fix was to load the items inside the handlers (I made a helper function) so the template gets a list of actual items, rather than ids, which makes the rendering pure so the change tracking works correctly.
Thanks @dmorneau! That was it. Within a <%= for ... %> loop in my render function, I was invoking functions with @socket as parameter instead of explicitly using assign properties (clearly an anti-pattern!). It appears that v0.10, as an optimization, does a static analysis of the contents of a for-loop, looking for the names of assign properties that changed, and then deciding whether to execute it or not. In v0.4, rendering would run the for-loops no matter what.
I will reopen this so we remove any assign from socket.assigns to avoid people running into said pitfalls. We should probably make it a struct or so like Ecto.Association.NotLoaded.