LiveView lifecycle hook with access to Conn?

One thing that irks me when working with LiveView is that if you define a live route in your router (e.g. live "/my-thing/:id", MyThing.Manage, :manage), there’s no way to do anything with conn for the initial page request other than creating separate scopes to attach plugs, which leads to ugly code like this:

  scope "/workspaces", Thing do
    pipe_through [:route1_specific_thing]

    live "/route1", Thing.Route1, :route1

  scope "/workspaces", Thing do
    pipe_through [:route2_specific_thing]

    live "/route2", Thing.Route2, :route2

  scope "/workspaces", Thing do
    pipe_through [:route3_specific_thing]

    live "/route3", Thing.Route3, :route3

Another option is to use a regular route rather than a live route, but that then requires setting up a controller that does whatever you need to do with conn, then calls live_render().

What I wish was possible would be another lifecycle hook for the live view (tentatively named route), that would run before mount, with conn in context, something like this:

defmodule Thing.Live.Manage do
  use Phoenix.LiveView

  def route(conn, params) do
    # Mess with conn.assigns or do whatever.

  def mount(params, _session, socket) do
    thing = load_thing(params)

       workspace_id: params.workspace_id

  def render(assigns) do
     <h1> I LOVE HTML</h1>

That would remove the need for extra controllers, and allow keeping all the live view’s code its own module.

Before we discuss the solution, let’s discuss the problem: what do you want to do with the connection before your LiveView?

1 Like

Two things, mainly:

  1. Access data added by plugs to conn.assigns
  2. Call shared functions that expects the conn interface.

Maybe I’m just doing it wrong, but I have a mix of LiveViews and regular old routes with controllers, and the shared functions written for the regular routes expects to be able to access conn.assigns to find (for example) the authentication data put there by phx.gen.auth. These are used to generate footers, headers and breadcrumbs that don’t need to be inside the LiveView.

Unless we want the future of Phoenix to be LiveView only, I think it makes sense to enable code re-use between LiveView and non-LiveView routes, where possible.

The issue is, that data is only sort of accessible. Data set on the conn can be accessed “directly” in the static render, since that is all one process flow. But in the subsequent live render the live view needs to be able to source all of that info from scratch. Work you do to load values on the conn you basically have to re-do in the live view anyway.

To take your specific example, auth data put on the conn works great for the static render, but isn’t available at all in the live render, so when you have to socket |> assign_new(:current_user, fn -> figure_out_current_user_again(session) end. Doing it on the conn doesn’t really save you much work.

This leads to a situation where what I’ve done at least is extract all of this logic to a MyAppWeb.Context module that creates a %MyAppWeb.Context{} struct which includes things like who the current user is, what their permissions are, and so forth. Then I have a plug which calls fetch_context and a function at the top of every live view which calls fetch_context and internally uses assign_new.

It’s that last bit that is really the most awkward for me. LiveView’s lack a plug like abstraction, and so I have this repetitive function call at the front of every live view. On the upside, it’s at least very explicit.

EDIT in 2022: LiveView now has Phoenix.LiveView — Phoenix LiveView v0.17.11 for its “plug like” abstraction to help make this more ergonomic.


Yeah, that’s actually a much better solution than my suggestion. I’ll steal that idea :wink:

Indeed, a plug-like API that worked for classic routes and LiveViews alike would be useful to reduce duplication.

1 Like

As Ben said, when you are doing a live request, the connection does not have any of the assigns. So having a route callback isn’t going to help here. We do talk about this in the docs and the approach followed by Ben here:


I see, thanks for explaining.