plugins/elixir-phoenix/skills/liveview-patterns/SKILL.md
Build LiveView: async data (assign_async), PubSub (check connected?), phx-change events, form components/modals/uploads, streams for lists, live_patch. Use when handling interactions, debugging events, or tracking Presence.
npx skillsauth add oliver-kriska/claude-elixir-phoenix liveview-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference for building with Phoenix LiveView 1.0/1.1.
assign_async. SEO routes: connected? guard + cache-backed disconnected branch (crawlers read that HTML){:error, changeset} first, not viewport/JShidden_input if not directly editableassign_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| 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
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
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)
For public/SEO-visible routes (marketing, articles, product listings) the disconnected render IS the HTML crawlers see. Fetch from a cache there, real data on connect:
def mount(_params, _session, socket) do
products =
if connected?(socket),
do: Catalog.list_products(),
else: Cache.get_products() || []
{:ok, assign(socket, products: products)}
end
Empty list → <noscript>-friendly skeleton. Cache → :persistent_term, ETS,
or Cachex. This satisfies Iron Law #1 AND keeps Googlebot/GPTBot happy.
def mount(_params, _session, socket) do
if connected?(socket), do: Chat.subscribe(room_id)
{:ok, socket}
end
Same LiveView, different params? → patch / push_patch
Different LiveView, same live_session? → navigate / push_navigate
Different live_session or non-LiveView? → href / redirect
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"
| 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) |
For detailed patterns, see:
${CLAUDE_SKILL_DIR}/references/async-streams.md - assign_async, stream_async, streams${CLAUDE_SKILL_DIR}/references/forms-uploads.md - Forms, validation, file uploads${CLAUDE_SKILL_DIR}/references/components.md - Function components, LiveComponents${CLAUDE_SKILL_DIR}/references/pubsub-navigation.md - PubSub, navigation, JS commands${CLAUDE_SKILL_DIR}/references/js-interop.md - Third-party JS libraries, phx-update="ignore", hooks${CLAUDE_SKILL_DIR}/references/channels-presence.md - Phoenix Channels, Presence, token authdevelopment
Verify Elixir/Phoenix changes — compile, format, and test in one loop. Use after implementation, before PRs, or after fixing bugs.
development
OTP/BEAM patterns and Elixir idioms — GenServer, Supervisor, Task, Registry, pattern matching, with chains, pipes. Use when designing processes or debugging BEAM issues.
tools
Self-improving loop for plugin skills. Reads program.md, proposes one mutation per iteration, evaluates against deterministic scorer, keeps improvements via git, reverts failures. Targets weakest skill+dimension. Use with /loop for overnight runs.
development
Project health audit and health check — architecture, performance, tests, dependencies, code quality. Use when assessing overall project health, before releases, or after refactors.