# Use router `on_mount` hooks instead of duplicating LiveView mount logic

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.

```elixir
# ❌ 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.

```elixir
# ✅ 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
```

```elixir
# 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
```

```elixir
# 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`:

```elixir
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**:

```elixir
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**:

```elixir
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**:

```elixir
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.


---

Created by: almirsarajcic
Date: October 22, 2025
URL: https://elixirdrops.net/d/6itDTwdu
