Have a live footer and header with LiveView

How can I do it? Somehow, when I render my header and footer leex templates, @socket does not have any assigns. I even tried making a LiveView named Footer and live_render it inside my live.html.leex but that did not work too, assigns is not still populated.

Is there any way to do it?

Not sure I understand you but I think you need to pass in the data throug the session (as a map with string keys).

<%= live_render @conn, MyAppWebb.FooterLive, session: %{"arg1" => val1} %>

No, I don’t mean how to pass data when rendering other LiveViews. I can’t seem to figure out how to share some templates (lees) between multiple LiveViews (e.g. a footer or a header).

I don’t know how to structure templates/LiveViews for this.

Have you looked into doing live layouts? Live layouts — Phoenix LiveView v0.15.5

Yes. have these lines in live.html.leex:

<%= live_render @socket, MyApp.Live.Navbar, session: %{"referrer" => @socket.view}, id: "navbar" %>
# main content here
<%= live_render @socket, MyApp.Live.Footer, session: %{"referrer" => @socket.view}, id: "footer" %>

And for example in MyApp.Live.Footer:

  @impl true
  def mount(_, %{"referrer" => ref}, socket) do
    Logger.debug(fn -> "Footer referrer #{ref}" end)
    {:ok, assign(socket, referrer: ref)}
  end

  def tab_active?(%{assigns: %{referrer: "Elixir.MyApp.Live.Pages.Something"}}, :something),
    do: true

  def tab_active?(socket, _), do: false

and its template:

<footer class="bg-white footer border-t border-gray-500 h-16 fixed bottom-0 left-0 right-0 z-30 shadow md:max-w-3xl md:mx-auto md:border-r md:border-l">
  <div id="bottom-nav-bar" class="text-gray-600 flex items-center justify-between divide-x divide-gray-500 h-full">
    <%= live_redirect to: Routes.some_path(@socket, :something
    class: (if tab_active?(@socket, :feed), do: "relative active", else: "relative") do %>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 inline-block mr-1" viewBox="0 0 20 20" fill="currentColor">
        <path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z" />
      </svg>
    <% end %>

    <%= live_redirect to: Routes.some2_path(@socket, :something2
    class: (if tab_active?(@socket, :network), do: "relative active", else: "relative") do %>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
      </svg>
    <% end %>
</footer>

But assigns in footer LiveView is not populated at all. It’s just an instance of Phoenix.LiveView.Socket.AssignsNotInSocket and is empty.

I don’t know how to structure this. I want some links in navbar and footer that I can make active using some assigns, when user is on certain pages, whether this assign can come from Footer or the main LiveView that is being rendered inside live.html.leex.

I’m lost.

You’re doing your header and footer as completely distinct live views with live_render. You probably should do live_component and make them components not distinct views. This will make passing in assigned values much much easier.

1 Like

That’s something I didn’t think of. I’ll try it. Thanks!

BTW, is this how people organize this kind of shared templates in LiveView?

Yes it is a very common pattern to use live components for this purpose.

2 Likes

Lately I’ve been going through my LiveView apps and isolating all the shared (and many unshared) front-end elements into LiveComponents (stateful if there is a form, otherwise stateless). It seems much better organized, and I haven’t run into any downsides to having lots of components yet.

2 Likes