LiveView Patterns Reference
Reference for building with Phoenix LiveView 1.0/1.1.
Iron Laws — Never Violate These
-
NO DATABASE QUERIES IN DISCONNECTED MOUNT — Queries run TWICE (HTTP + WebSocket). Use assign_async
-
ALWAYS USE STREAMS FOR LISTS — Regular assigns = O(n) memory per user. Streams = O(1)
-
CHECK connected?/1 BEFORE SUBSCRIPTIONS — Prevents double subscriptions
-
EXTRACT VARIABLES BEFORE assign_async CLOSURE — Closures copy entire referenced variables
-
LOAD PRIMARY DATA IN mount/3, PAGINATION IN handle_params/3 — handle_params runs on EVERY URL change
-
NEVER PASS SOCKET TO BUSINESS LOGIC — Extract data before calling contexts
-
CHECK CHANGESET ERRORS BEFORE UI DEBUGGING — Silent form save = check {:error, changeset} first, not viewport/JS
-
HIDDEN INPUTS FOR ALL REQUIRED EMBEDDED FIELDS — Every required field in an embedded schema MUST have a hidden_input if not directly editable
-
NEVER USE assign_new FOR LIFECYCLE VALUES — assign_new skips the function if key exists. Use assign/3 for locale, current user, or any value refreshed every mount
Memory Impact
Pattern 3K items 10K users × 10K items
Regular assigns ~5.1 MB ~10+ GB
Streams ~1.1 MB Minimal (O(1))
Decision: Lists with >100 items → Use streams, not assigns
Quick Patterns
Async Assigns (CRITICAL)
def mount(%{"slug" => slug}, _session, socket) do
Extract needed values BEFORE the closure
scope = socket.assigns.current_scope
{:ok, socket |> assign_async(:org, fn -> {:ok, %{org: fetch_org(scope, slug)}} end)} end
Streams for Lists
def mount(_params, _session, socket) do {:ok, stream(socket, :items, Items.list_items())} end
Insert/update/delete
stream_insert(socket, :items, item, at: 0) stream_delete(socket, :items, item)
PubSub with connected? check
def mount(_params, _session, socket) do if connected?(socket), do: Chat.subscribe(room_id) {:ok, socket} end
Navigation Decision Tree
Same LiveView, different params? → patch / push_patch Different LiveView, same live_session? → navigate / push_navigate Different live_session or non-LiveView? → href / redirect
Component Decision Tree
Does component need BOTH internal state AND event handling? │ ├── YES → Does it encapsulate APPLICATION logic (not just DOM)? │ ├── YES → Use LiveComponent ✅ │ └── NO → Refactor to function component with parent handling │ └── NO → Use Function Component ✅
Official guidance: "Prefer function components over live components"
Common Anti-patterns
Wrong Right
DB queries without assign_async
Use assign_async for all queries
assign(socket, items: list) for lists stream(socket, :items, list)
PubSub subscribe without connected?
if connected?(socket), do: subscribe()
Passing socket to context functions Extract socket.assigns first
Business logic in handle_event
Delegate to context
assign_new for locale/user in hooks assign/3 (must run every mount)
References
For detailed patterns, see:
-
references/async-streams.md
-
assign_async, stream_async, streams
-
references/forms-uploads.md
-
Forms, validation, file uploads
-
references/components.md
-
Function components, LiveComponents
-
references/pubsub-navigation.md
-
PubSub, navigation, JS commands
-
references/js-interop.md
-
Third-party JS libraries, phx-update="ignore", hooks
-
references/channels-presence.md
-
Phoenix Channels, Presence, token auth