LiveComponent Conditional Rendering Issue Using a For Comprehension

phoenix 1.6.2 and phoenix_live_view 0.17.5

I have an issue in reliably getting the appropriate LiveComponent rendering to occur when changing a schema value in the DB via Repo.update and immediately re-rendering from a Repo.all DB query. The re-render intermittently works properly, but mostly does not, primarily when changing a class struct archived value back to its default false value. Changing selected state does not have the issue.

I first thought that the DB query was returning old values, since the query occurred immediately after the update. Refreshing the page always results in correct rendering.

I exposed the modified struct values in the UI for debugging purposes, and logged the query results to confirm that the DB returns the proper data.

The images below show default state, then changing archived state to true, followed by changing archived state back to false but the archived LiveComponent is rendered rather than the default. In addition, intermittently, the default LiveComponent is rendered when the archived state is set to true. The DB query provides a sorted response, with archived components at the bottom.

image

image

image

Here is the for comprehension code from a HEEx file, for rendering default, archived, and selected states (and displaying archived state for debugging purposes). Note, the selected state always renders properly.

        <%= for class <- @classes do %>
          Class: <%= class.title %> - Archived <%= class.archived %>
          <%= if class.archived do %>          
            <.live_component module={RitoWeb.Components.ArchivedClass} id={class.id} class={class} user={@user} username={@username} color={class.color_code} back_color="#f0f0f0" />
          <% else %>
            <%= if Rito.Util.class_selected?(class.id, @selected_class) do %>
              <.live_component module={RitoWeb.Components.Class} id={class.id} class={class} user={@user} username={@username} color={class.color_code} selected="true" back_color="#e7facc" />
            <% else %>
              <.live_component module={RitoWeb.Components.Class} id={class.id} class={class} user={@user} username={@username} color={class.color_code} back_color="#fff" />
            <% end %>
          <% end %>
        <% end %>

Try adding some kind of id inside the comprehension to bind the data to the DOM better. It would make morphdom use keyed updates.

<%= for class <- @classes do %>
  <div id={class.id}>
    # ...

I’m passing id as an attribute

image

and here is the id being assigned in the LiveComponent div

<div class="flex flex-row shadow-md flex-grow border-solid border mb-4 p-2 rounded-md hover:shadow-mdh hover:border-brand-color cursor-pointer" style={"background-color:#{@back_color};"} phx-click="select_class" phx-value-id={@id} id={@id}>
  <div style={"background-color:#{@color};"} class="w-4 mr-4 border-solid border border-brand-color">
  </div>
  <div class="flex-col">
    <div>
      <b><%= @class.title %></b>
    </div>
    <div>
      <%= @username %>
    </div>
  </div>

...

</div>

I suggest trying it like this first:

<%= for class <- @classes do %>
  <div id={"class-item-" <> class.id}>
    Class: <%= class.title %> - Archived <%= class.archived %>
    # ...

In your original example the immediate text is not keyed in any way, and as far as I understand, it’s the problem. Google for keyed vs non-keyed DOM updates for more info.

Here’s an interactive example: Logic / Keyed each blocks • Svelte Tutorial

2 Likes

Thank you. I first wrapped a div with an id around the immediate text and the LiveComponent render command, and removed id from the LiveComponent’s wrapping div, causing an error. I then included both the wrapping div and passing an id parameter with the LiveComponent render, and the rendering problems disappeared.

In summary, adding a wrapping div with an id in the for comprehension and removing the id= from the LiveComponent’s wrapping div, while passing an id attribute in the LiveComponent render command, will allow the for comprehension to work properly.

Since the problem I identified occurs even with no immediate text in the for comprehension, I think there continues to be an issue where passing an id attribute in the LiveComponent render command, should allow proper DOM manipulation.

I followed up with removing the immediate text in the for comprehension and all works well. Here is the working for comprehension with wrapping div:

        <%= for class <- @classes do %>
          <%= if class.archived do %>          
            <div id = {class.id}>
              <.live_component module={RitoWeb.Components.ArchivedClass} class={class} user={@user} username={@username} color={class.color_code} back_color="#f0f0f0" id={class.id} />
            </div>
          <% else %>
            <%= if Rito.Util.class_selected?(class.id, @selected_class) do %>
              <div id = {class.id}>
                <.live_component module={RitoWeb.Components.Class} class={class} user={@user} username={@username} color={class.color_code} selected="true" back_color="#e7facc" id={class.id} />
              </div>
            <% else %>
              <div id = {class.id}>
                <.live_component module={RitoWeb.Components.Class} class={class} user={@user} username={@username} color={class.color_code} back_color="#fff" id={class.id} />
              </div>
            <% end %>
          <% end %>
        <% end %>

Here is the working LiveComponent wrapping div with no id=:

<div class="flex flex-row shadow-md flex-grow border-solid border mb-4 p-2 rounded-md hover:shadow-mdh hover:border-brand-color cursor-pointer" style={"background-color:#{@back_color};"} phx-click="select_class" phx-value-id={@id}>
  <div style={"background-color:#{@color};"} class="w-4 mr-4 border-solid border border-brand-color">
  </div>
  <div class="flex-col">
    <div>
      <b><%= @class.title %></b>
    </div>
    <div>
      <%= @username %>
    </div>
  </div>

...

</div>

I generated an issue on LiveView for the underlying issue with expected behavior

UPDATE: @josevalim pointed out that the root cause of this issue was passing an integer value in the id attribute