Skip to content
cccskills
FreePublished on GitHub

liveview-patterns

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.

The English summary is being prepared. Showing the original description.

See how to install

Files included (7)

  • SKILL.md5.3 KB
  • references/async-streams.md5.6 KB
  • references/channels-presence.md5.1 KB
  • references/components.md2.7 KB
  • references/forms-uploads.md3.3 KB
  • references/js-interop.md8.1 KB
  • references/pubsub-navigation.md4.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

LiveView Patterns Reference

Ash projects: Use ash-framework skill for AshPhoenix.Form. Lifecycle: AshPhoenix.Form.validate/3 on phx-change, AshPhoenix.Form.submit/2 on submit, to_form/1 for HEEx. Do not use Ecto.Changeset.cast/3.

Reference for building with Phoenix LiveView 1.0/1.1.

Iron Laws — Never Violate These

  1. NO UNCONDITIONAL DB QUERIES IN MOUNT — Mount runs TWICE. Default: assign_async. SEO routes: connected? guard + cache-backed disconnected branch (crawlers read that HTML)
  2. ALWAYS USE STREAMS FOR LISTS — Regular assigns = O(n) memory per user. Streams = O(1)
  3. CHECK connected?/1 BEFORE SUBSCRIPTIONS — Prevents double subscriptions
  4. EXTRACT VARIABLES BEFORE assign_async CLOSURE — Closures copy entire referenced variables
  5. LOAD PRIMARY DATA IN mount/3, PAGINATION IN handle_params/3 — handle_params runs on EVERY URL change
  6. NEVER PASS SOCKET TO BUSINESS LOGIC — Extract data before calling contexts
  7. CHECK CHANGESET ERRORS BEFORE UI DEBUGGING — Silent form save = check {:error, changeset} first, not viewport/JS
  8. HIDDEN INPUTS FOR ALL REQUIRED EMBEDDED FIELDS — Every required field in an embedded schema MUST have a hidden_input if not directly editable
  9. 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
  10. MATCH {:error, %Ecto.Changeset{}} EXPLICITLY — Bare {:error, _} merges changeset and non-changeset errors; the form silently never re-renders validation errors. Handle other errors separately

Memory Impact

Pattern3K items10K users × 10K items
Regular assigns~5.1 MB~10+ GB
Streams~1.1 MBMinimal (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)

SEO Dead-Render (cache-backed disconnected branch)

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.

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

WrongRight
DB queries without assign_asyncUse assign_async for mount queries (SEO routes: connected? + cached dead render)
assign(socket, items: list) for listsstream(socket, :items, list)
PubSub subscribe without connected?if connected?(socket), do: subscribe()
Passing socket to context functionsExtract socket.assigns first
Business logic in handle_eventDelegate to context
assign_new for locale/user in hooksassign/3 (must run every mount)

References

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 auth

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Analyze a legacy app in any language before rewriting it in Elixir: inventory, features as goals, why each mechanism exists, runtime topology, external contracts. Use to start or refresh a rewrite.

The English summary is being prepared. Showing the original description.

oliver-kriska/claude-elixir-phoenix567Updated Oct 17, 2026

Design the idiomatic Elixir architecture for a rewrite: set the compatibility bar (ADR-000), consult phx specialists on open questions, then build contexts, ledger and ADRs. Use after /rewrite:analyze.

The English summary is being prepared. Showing the original description.

oliver-kriska/claude-elixir-phoenix567Updated Oct 17, 2026

Ash Framework — resources, actions, policies, aggregates; Use when generating resources via mix ash.codegen, editing…

The English summary is being prepared. Showing the original description.

oliver-kriska/claude-elixir-phoenix567Updated Oct 17, 2026

Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.

The English summary is being prepared. Showing the original description.

oliver-kriska/claude-elixir-phoenix567Updated Oct 17, 2026

Compatibility alias for the Elixir/Phoenix plugin's LiveView assigns audit. Invoke explicitly with /lv:assigns.

The English summary is being prepared. Showing the original description.

oliver-kriska/claude-elixir-phoenix567Updated Oct 17, 2026

Inspect LiveView socket assigns for memory bloat — missing temporary_assigns, unused assigns, unbounded lists needing streams, memory estimates. Use when LiveView memory grows or you need to add temporary_assigns.

The English summary is being prepared. Showing the original description.

oliver-kriska/claude-elixir-phoenix567Updated Oct 17, 2026

See all skills by oliver-kriska

Report a problem with this skill