We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Use router `on_mount` hooks instead of duplicating LiveView mount logic
almirsarajcic
Copy-pasting the same assigns across every LiveView mount/3
function creates maintenance nightmares. Router-level on_mount
hooks run automatically before mount, eliminating duplication.
# ❌ DUPLICATED LOGIC - Copy-pasted across 15+ LiveViews
defmodule JobsLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
preferences = Preferences.for_user(socket.assigns.current_user)
socket =
socket
|> assign(:theme, preferences.theme)
|> assign(:timezone, preferences.timezone)
|> assign(:page_title, "Jobs")
{:ok, socket}
end
end
defmodule CandidatesLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
preferences = Preferences.for_user(socket.assigns.current_user)
socket =
socket
|> assign(:theme, preferences.theme)
|> assign(:timezone, preferences.timezone)
|> assign(:page_title, "Candidates")
{:ok, socket}
end
end
# ...repeat in 13 more LiveViews
Every LiveView duplicates preference loading. When you add a new preference field, you update 15+ files or risk inconsistent behavior.
# ✅ CENTRALIZED HOOK - Write once, apply everywhere
defmodule MyAppWeb.PreferencesHook do
import Phoenix.LiveView
def on_mount(:load_preferences, _params, _session, socket) do
preferences = Preferences.for_user(socket.assigns.current_user)
socket =
socket
|> assign(:theme, preferences.theme)
|> assign(:timezone, preferences.timezone)
{:cont, socket}
end
end
# Router - Apply to all LiveViews in live_session
scope "/app", MyAppWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :authenticated,
on_mount: [
{MyAppWeb.UserAuth, :ensure_authenticated},
{MyAppWeb.PreferencesHook, :load_preferences}
] do
live "/jobs", JobsLive, :index
live "/candidates", CandidatesLive, :index
live "/analytics", AnalyticsLive, :index
end
end
# LiveViews - Clean mount functions
defmodule JobsLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
# :theme and :timezone already loaded by hook
{:ok, assign(socket, :page_title, "Jobs")}
end
end
defmodule CandidatesLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
# :theme and :timezone already loaded by hook
{:ok, assign(socket, :page_title, "Candidates")}
end
end
Now changing preference loading logic requires updating one hook module instead of 15+ LiveViews.
Hook execution order
Hooks run in the order specified, before each LiveView’s mount/3
:
live_session :authenticated,
on_mount: [
{MyAppWeb.UserAuth, :ensure_authenticated}, # 1. Verify authentication
{MyAppWeb.PreferencesHook, :load_preferences}, # 2. Load preferences (needs current_user)
{MyAppWeb.AnalyticsHook, :track_pageview} # 3. Track analytics
] do
live "/dashboard", DashboardLive, :index
end
Each hook returns {:cont, socket}
to continue, or {:halt, socket}
to stop (useful for redirects in auth checks).
Common use cases for on_mount hooks
Authentication/Authorization:
def on_mount(:ensure_admin, _params, _session, socket) do
if socket.assigns.current_user.role == :admin do
{:cont, socket}
else
{:halt, redirect(socket, to: "/")}
end
end
Feature flags:
def on_mount(:load_features, _params, _session, socket) do
features = FeatureFlags.for_user(socket.assigns.current_user)
{:cont, assign(socket, :features, features)}
end
Analytics tracking:
def on_mount(:track_pageview, _params, _session, socket) do
if connected?(socket) do
Analytics.track_pageview(socket.assigns.current_user)
end
{:cont, socket}
end
Hooks keep your LiveView mount/3
functions focused on page-specific logic while centralizing cross-cutting concerns at the router level.
copied to clipboard