How to use LiveComponents in Layouts, in the latest live view?

I want to be able to have interactive elements. i.e.

  1. Top and side nav, with live interaction.
  2. Being able to change the layout using live_session.

Is it frowned upon to carry the state in layouts?
Will a filter search work in a sidebar?
What about a directory kind of view on the sidebar?


How does everyone do it?

2 Likes

Some relevant discussion here:

2 Likes

And here Wrapping live components with functional components

1 Like

I found the solution by hit and trial:

live_session :authenticated,
  on_mount: [{DerpyCoder.UserAuth, :ensure_authenticated},
             {DerpyCoder.HeaderData, :fetch_existing_notifications}],
  root_layout: {DerpyCoder.Layouts, :admin},
  layout: {DerpyCoder.Layouts, :admin_header} do
...
end

admin.html.heex (Root Layout) [Same as root.html.heex]

<html>
...
<.only_function_components_allowed />
</html>

admin_header.html.heex (Live Components Allowed) [Same as app.html.heex]

<main>
  <.flash_group flash={@flash} />
  <.live_component module={DerpyCoder.SomeCustomLiveComponent} id={:yay} />
  <%= @inner_content %>
</main>

Excerpt from documentation:

  • :root_layout - The optional root layout tuple for the initial HTTP render to override any existing root layout set in the router.
  • :layout - The optional layout the LiveView will be rendered in. Setting this option overrides the layout via use Phoenix.LiveView. This option may be overridden inside a LiveView by returning {:ok, socket, layout: ...} from the mount callback

So by using a layout, along with a root layout, I was able to put the LiveComponent into the layout.
It seems the root layout is dead view, used for flushing out the styling and just works over HTTP.

While the layout is live, and thus allowed me to embed a Live Component.

Plus we can initialize the layout using on_mount and it has access to all the assigns from a page.


And one more approach is to just rely on on_mount, to handle data fetching and handle interactions.

As done by live_beats.

live_session :authenticated,
  on_mount: [{LiveBeatsWeb.UserAuth, :ensure_authenticated}, LiveBeatsWeb.Nav] do
    live "/:profile_username/songs/new", ProfileLive, :new
    live "/:profile_username", ProfileLive, :show
    live "/profile/settings", SettingsLive, :edit
end
defmodule LiveBeatsWeb.Nav do
  import Phoenix.LiveView
  use Phoenix.Component

  def on_mount(:default, _params, _session, socket) do
    {:cont,
       socket
       |> assign(active_users: MediaLibrary.list_active_profiles(limit: 20))
       |> assign(:region, System.get_env("FLY_REGION") || "iad")
       |> attach_hook(:active_tab, :handle_params, &handle_active_tab_params/3)
       |> attach_hook(:ping, :handle_event, &handle_event/3)}
  end

  ...
end

I’m not familiar with this approach, it’s my first time seeing attach_hook, so I will have to read through live_beats repo to understand it.

Thank you @cmo.


P.S. I am trying to combine both approach in my project, so I will update my findings here.

4 Likes

Another alternative (and the one I’m currently using for a notification nav shared in both liveviews and deadviews) is to live_render a liveview as LiveBeats does here. I do this in the root layout however.

1 Like

Thank you, I was thinking about how the player in LiveBeats works across pages.