LiveView Lifecycle
Mount
connected?/1 for Subscriptions
def mount(_params, _session, socket) do
# Only subscribe when actually connected (not during static render)
if connected?(socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "updates")
end
{:ok, assign(socket, items: [])}
end
Expensive Operations
# BAD - blocks initial render
def mount(_params, _session, socket) do
items = Repo.all(Item) # Blocks!
{:ok, assign(socket, items: items)}
end
# GOOD - defer with assign_async
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:page_title, "Items")
|> assign_async(:items, fn -> {:ok, %{items: Repo.all(Item)}} end)}
end
handle_params
URL-Based State
def handle_params(%{"page" => page}, _uri, socket) do
page =
case Integer.parse(page) do
{n, ""} when n > 0 -> n
_ -> 1
end
{:noreply, assign(socket, page: page, items: load_page(page))}
end
def handle_params(_params, _uri, socket) do
{:noreply, assign(socket, page: 1, items: load_page(1))}
end
Live Navigation
# Triggers handle_params, not full remount
<.link patch={~p"/items?page=2"}>Page 2</.link>
# Full remount (different LiveView)
<.link navigate={~p"/other"}>Other Page</.link>
handle_event
Pattern Match Events
def handle_event("save", %{"form" => form_params}, socket) do
# Handle save
end
def handle_event("delete", %{"id" => id}, socket) do
# Handle delete
end
def handle_event("toggle", %{"value" => value}, socket) do
# Handle toggle
end
Form Events
def handle_event("validate", %{"user" => params}, socket) do
changeset =
socket.assigns.user
|> User.changeset(params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, changeset: changeset)}
end
def handle_event("save", %{"user" => params}, socket) do
case Accounts.update_user(socket.assigns.user, params) do
{:ok, user} ->
{:noreply,
socket
|> put_flash(:info, "Saved!")
|> push_navigate(to: ~p"/users/#{user}")}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
handle_async
With assign_async
def mount(_params, _session, socket) do
{:ok,
socket
|> assign_async(:user, fn -> {:ok, %{user: load_user()}} end)}
end
# In template - handle all states
<.async_result :let={user} assign={@user}>
<:loading>Loading user...</:loading>
<:failed :let={reason}>Error: <%= inspect(reason) %></:failed>
<%= user.name %>
</.async_result>
With start_async
def handle_event("refresh", _, socket) do
{:noreply, start_async(socket, :refresh, fn -> fetch_data() end)}
end
def handle_async(:refresh, {:ok, data}, socket) do
{:noreply, assign(socket, data: data)}
end
def handle_async(:refresh, {:exit, reason}, socket) do
{:noreply, put_flash(socket, :error, "Refresh failed")}
end
Review Questions
- Are PubSub subscriptions wrapped in connected?(socket)?
- Is handle_params used for URL-based state changes?
- Do async operations handle both success and failure?
- Is expensive loading deferred with assign_async?