`app.html.heex` not invoking functions

Background

I have Phoenix LiveView application called web_interface. This application has a layout file under the normal predefined path: web_interface/lib/web_interface/components/layouts/app.html.heex.

I want to show the navigation bar of my application only if the following conditions apply:

  • A User is logged in
  • No operations are currently in progress

Code

To achieve this I have the follow code:

<header>
  <%= if Storage.User.has_user?() and not Storage.OperationProgress.in_progress?() do %>
    <nav class="bg-gray-800">
      <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
        <!-- buttons -->
          <a
            href={~p"/activate"}
            class={
              if Storage.Button.button_selected?(:activate) do
                "bg-gray-900"
              else
                "hover:bg-gray-700"
              end
            }
          > Activate Btutton </a>
      </div>
    </nav>
  <% end %>
</header>
<main class="h-full mx-auto">
  <.flash_group flash={@flash} />
  <%= @inner_content %>
</main>

Basically I have a Storage module that uses ETS to store data, and when the page layout is loaded, I check for my conditions.

Problem

The problem here is that even the button Activate Btutton changes color correctly (depending on its state, if it is selected or not), the nav bar does not.

To be more precise, if a user is not logged in, the nav bar does not show (as expected).
However, if an operation is in progress, the nav bar should hide, but it still appears.

What I tried

The normal reaction would be to assume that Storage.OperationProgress.in_progress?() is returning an incorrect value. This is not the case, as when I start an operation and manually invoke the function, it returns the correct values.

I also tried moving part of the condition into the nav bar’s class:

<nav class={
      if not Storage.OperationProgress.in_progress?() do
        "hidden"
      else
        "bg-gray-800"
      end
    }>

However this also did not work.

At this point I assumed tha app.html.heex is only run once, when rendering. However this also proved to be false, as the check for the button color does work properly:

  if Storage.Button.button_selected?(:activate) do
                "bg-gray-900"
              else
                "hover:bg-gray-700"
              end

So at this point I either miss-interpreted something and I am missing something.
Can someone help me trouble shoot?

I expect you’re talking about LiveView, because otherwise nothing would change in the first place.

In LV templates rerender based on assigns changing and the change tracking made based on assigns. So generally given you’re not using any assigns in your header you cannot expect it to update. The button updating could be caused due to non-optimal attempts in splitting up static parts of your template with ones affected by assigns.

That splitting up static parts from dynamic parts also means that heex does granularly rerender changed things. Only the parts actually affected by the changed assign is meant to be updated. Anything else will stay as is – as in your functions with side effects won’t necessarily be called again.

Yes, this is related to LV.
iirc app.html.heex cannot use assigns, as it is the first file rendered in the life cycle.
Please let me know if I am incorrect.

That is incorrect. The layout uses the assigns of the root LV (router mounted).

LiveViews are re-rendered when their assigns change. You should avoid calling out to global state from within the template because there’s no way for it to know to re-render when the state changes. Check out the LiveView guides (they’re really good), particularly:

You should store your state locally within the LiveView process and update it via assign/3.

If the state is actually global (like, it needs to be shared between users) then you will have to tell the LiveView about changes using Phoenix PubSub.

I can’t say for sure why your button appears to be working without seeing the rest of your code, but it’s probably due to something else causing the LiveView to re-render which then shows the correct state. In other words, it’s working by coincidence but the implementation is not correct.

1 Like

While your post is of course correct, it is also worth pointing out (to avoid confusion) that the root layout (distinct from the app layout) cannot be updated after the first render. See:

https://hexdocs.pm/phoenix_live_view/live-layouts.html

If you inspect the HTML of your LiveView page you will see quite clearly why: the root layout is literally “outside” the mounted LiveView.

You should store your state locally within the LiveView process and update it via assign/3.

If the state is actually global (like, it needs to be shared between users) then you will have to tell the LiveView about changes using Phoenix PubSub.

I can’t say for sure why your button appears to be working without seeing the rest of your code, but it’s probably due to something else causing the LiveView to re-render which then shows the correct state. In other words, it’s working by coincidence but the implementation is not correct.

This turned out to be the case. The reason my button was working its because it is not really a button, but a link to a different LV page instead, which reloads the app.html.heex and is then able to access the global state necessary on the start.

The reason why the navbar was not hiding itself, its because after the initial reload, nothing else is updated, unless I used assigns.

Using assigns solved that issue. Using assigns in the mount function for the buttons made me not need to use the global state.

Overall I am very happy! I have a lot of refactoring to do, but I am doing it knowing I learned something. Amazing Experience!