Accessing Shared State in Custom Phoenix LiveView Layout

Hey everyone!

I’m currently working on a new Phoenix LiveView app with the following setup:

Phoenix: 1.7.14
LiveView: 1.0 rc6

Layout Overview

My layout structure looks like this:

root.html.heex renders the live.html.heex content.
live.html.heex initially renders HomeLive for the ‘/’ route.

Layout Components:

site_header (function component)
sidebar (function component)
footer (function component)
landing_page (rendered inside HomeLive)

The site_header and sidebar components are rendered outside of HomeLive, whereas the main content area, including the landing_page, is rendered inside HomeLive.

Here’s a sample snippet from live.html.heex:

<main class="...">
  <.sidebar_container />

  <div class="...">
    <.site_header />

    <div class="...">
      <div class="...">
        <.flash_group flash={@flash} />
        <%= @inner_content %>
      </div>
    </div>

    <.footer />
  </div>
</main>

Problem Statement

I’m trying to set up the initial state using a module similar to nav.ex in the live_beats app ( fly-apps/live_beats (github.com)):

def on_mount(:default, _params, _session, socket) do
  # sets assigns, streams, etc.
end

In live_beats, nav.ex is called/applied in the router:

scope "/", LiveBeatsWeb do
  pipe_through :browser

  live_session :default, on_mount: [{LiveBeatsWeb.UserAuth, :current_user}, LiveBeatsWeb.Nav] do
    live "/signin", SignInLive, :index
  end

  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
end

This setup makes the state in nav.ex available, so the sidebar can access @active_tab and other shared assigns.

Issue

I tried a similar setup in my app:

scope "/", MyAppWeb do
  pipe_through :browser

  live_session :default,
    on_mount: [MyAppWeb.Nav] do
    live "/", HomeLive
  end
end

However, in my layout, only HomeLive has access to the state defined in nav.ex. The function components like sidebar and header (which are rendered outside of HomeLive) don’t have direct access to it. For instance, I can’t access @sidebar_open in the sidebar component.

Question

How can I make the initial state defined in lib/my_app_web/live/nav.ex accessible to my layout components like the sidebar and header? I’d like them to have access to shared state without duplicating logic.

I’ve done some research but couldn’t find a reliable solution, hence reaching out here. Any guidance or patterns to achieve this would be greatly appreciated!

Thanks in advance! :pray:

1 Like

If your liveview has access to the assigns set by ‘Nav’ on the mount event, then this liveview should propagate the assigns to any embedded function components.

Someone from Elixir Discord Server pointed out that live.html.heex (my application layout) will live-update, and assigns set in nav.ex will be available in my layout components - and he was right.

My configuration was correct:

scope "/", MyAppWeb do
    pipe_through :browser

    live_session :default,
      on_mount: [MyAppWeb.Nav] do
      live "/", HomeLive
    end
  end

But somehow was not working, and after stopping and restarting the server, it worked and assigns in nav.ex are now available in my layout components.