Sticky Aside with get_connect_params – How to Ensure Correct Initial State on Mount?

Hi,

I’m working on a Phoenix LiveView project where I have a sticky aside that should persist its open/closed state across page navigations. I pass the initial state (1drawer_open1) in the LiveSocket parameters and then use a hook to sync UI state changes to local storage. This works fine without the sticky option, but when sticky is enabled, the parent mount runs twice (which I think is part of the standard LV lifecycle):

  • First mount: drawer_open = get_connect_params(socket)["drawer_open"] returns nil
  • Second mount: The correct value is present, but the sticky aside has already rendered using the initial (nil) value from the session.
let liveSocket = new LiveSocket("/live", Socket, {
  longPollFallbackMs: 2500,
  params: {
    _csrf_token: csrfToken,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    ...initialUIState,
  },
  hooks,
});

The in the parent liveview:

def mount(_params, _session, socket) do
    drawer_open = get_connect_params(socket)["drawer_open"]

    socket =
      socket
      |> assign(drawer_open: drawer_open)

    {:ok, socket}
  end

Which passes it to the sticky liveview:

{live_render(@socket, Aside,
  id: "aside",
  session: %{
    "view" => @live_action,
    "drawer_open" => @drawer_open
  },
  sticky: true
)}

And the sticky LV:

  def mount(_params, session, socket) do
    drawer_open = session["drawer_open"]

    socket =
      socket
      |> assign(drawer_open: drawer_open)

    {:ok, socket}
  end

Because the sticky view renders on the first pass, subsequent changes (on the second mount) are ignored. Without sticky, the navigation feels janky, so I really need the sticky behavior along with the correct persisted state.

Question:
How can I ensure that the sticky aside mounts with the correct initial value from get_connect_params(socket)? Are there recommended approaches to delay rendering or update the sticky component after the correct value is obtained?

Any insights or workarounds would be greatly appreciated!

Could you use cookies to persist drawer state (opened/closed) and then use a plug to pass cookie value to session?

ie.

Create a plug:

  def put_drawer_state_session(conn, _opts) do
    put_session(conn, :drawer_state, get_cookies(conn)["drawer_state"])
  end

…and then use it in :browser pipeline in Router:

plug :put_drawer_state_session

…and then in a mount you could access drawer_state in session:

def mount(_params, session, socket) do
  # Use session[:drawer_state]