Currently, there are two ways to add on_mount hooks to a liveview, you either add them in the live_session macro, or inside each liveview using the on_mount/1 macro.
The order that they will be executed is first the hooks in the live_session call, and then the on_mount/1 calls inside the liveview in order.
Also, live_session calls don’t support having other live_session calls inside them.
This is a bit limiting in some scenarios and make a lot of duplicated code in some complex routers.
Example
In my route, I want to have the following hooks being attached to the liveview on_mount:
“/auth” route (contains routes to sign_in and sign_up):
- Hooks.GetLocale
- Hooks.GetTimezone
- {Hooks.Authentication, :not_authenticated}
"/admin"route (contains routes that needs the user to be logged):
- Hooks.GetLocale
- Hooks.GetTimezone
- {Hooks.Authentication, :authenticated}
Notice that both routes require the Hooks.GetLocale and Hooks.GetTimezone on_mount hooks.
So either I need to write two live_session calls that repeats the common hooks in my router:
scope "/auth", Live.Auth do
live_session :not_authenticated,
on_mount: [Hooks.GetLocale, Hooks.GetTimezone, {Hooks.Authentication, :not_authenticated}] do
...
end
end
scope "/admin", Live.Admin do
live_session :authenticated,
on_mount: [Hooks.GetLocale, Hooks.GetTimezone, {Hooks.Authentication, :authenticated}] do
...
end
end
Or I can add Hooks.GetLocale and Hooks.GetTimezone to my live_view function (because every liveview in my system should execute these two hooks anyway):
def live_view do
quote do
use Phoenix.LiveView,
layout: ...
on_mount Hooks.GetLocale
on_mount Hooks.GetTimezone
unquote(html_helpers())
end
end
And then my route becomes simpler:
scope "/auth", Live.Auth do
live_session :not_authenticated, on_mount: {Hooks.Authentication, :not_authenticated} do
...
end
end
scope "/admin", Live.Admin do
live_session :authenticated, on_mount: {Hooks.Authentication, :authenticated} do
...
end
end
This second example is better IMO since I don’t need to repeat a long list of on_mount hooks that are common to all my liveviews, but they just works fine as long as no hook in my live_session requires some value first set by these hooks set in the live_view function.
The reason is that the live_session hooks will be executed first and then the ones set in the live_view function. So, for the /auth route, the order would be:
- {Hooks.Authentication, :not_authenticated}
- Hooks.GetLocale
- Hooks.GetTimezone
If Hooks.Authentication requires the locale value, then it will fail because the Hooks.GetLocale is being executed after it.
I see three ways to fix this, the first one is the first example I shown in this post, basically I repeat all the common hooks in all live_session calls I have in my router.
This option can become a chore really fast if I have a bunch of common hooks that I want to add, makes the router harder to read and more verbose.
A second solution would be to add a prepend? option to the on_mount/1 call, that way, I can write it like this:
on_mount Hooks.GetLocale, prepend?: true
on_mount Hooks.GetTimezone, prepend?: true
And with this I would get this order of execution:
- Hooks.GetTimezone
- Hooks.GetLocale
- {Hooks.Authentication, :not_authenticated}
The third solution would be to allow live_session (or a simplified version of that that only allows to set the on_mount option) to be defined inside another live_session, for example:
live_session :common, on_mount: [Hooks.GetLocale, Hooks.GetTimezone] do
scope "/auth", Live.Auth do
live_session :not_authenticated, on_mount: {Hooks.Authentication, :not_authenticated} do
...
end
end
scope "/admin", Live.Admin do
live_session :authenticated, on_mount: {Hooks.Authentication, :authenticated} do
...
end
end
end
IMO that would be the best approach since it will allow to scope it the same way we do with the scope macro.
What do you thing about this suggestion? Do you know another way to achieve the same?






















