liveview-patterns
Build LiveView: async data (assign_async), PubSub (check connected?),
它会碰到什么
这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。
技能内容
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
- NO UNCONDITIONAL DB QUERIES IN MOUNT — Mount runs TWICE. Default:
assign_async. SEO routes:connected?guard + cache-backed disconnected branch (crawlers read that HTML) - 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_inputif not directly editable - NEVER USE
assign_newFOR LIFECYCLE VALUES —assign_newskips the function if key exists. Useassign/3for locale, current user, or any value refreshed every mount - 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
| 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)
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
| 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, streamsreferences/forms-uploads.md- Forms, validation, file uploadsreferences/components.md- Function components, LiveComponentsreferences/pubsub-navigation.md- PubSub, navigation, JS commandsreferences/js-interop.md- Third-party JS libraries, phx-update="ignore", hooksreferences/channels-presence.md- Phoenix Channels, Presence, token auth
想直接用这个技能?
本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。
它属于哪个仓库
targets/pi/skills/liveview-patterns/SKILL.md同一个仓库里的其他技能
同名技能的其他版本
有 6 个不同仓库或目录里都有叫 liveview-patterns 的技能。它们内容并不相同,别混用:
- oliver-kriska/claude-elixir-phoenix — Build LiveView: async data (assign_async), PubSub (check connected?), phx-change events, f
- oliver-kriska/claude-elixir-phoenix — Build LiveView: async data (assign_async), PubSub (check connected?),
- oliver-kriska/claude-elixir-phoenix — Build LiveView: async data (assign_async), PubSub (check connected?);
- oliver-kriska/claude-elixir-phoenix — Build LiveView: async data (assign_async), PubSub (check connected?),
- oliver-kriska/claude-elixir-phoenix — Build LiveView: async data (assign_async), PubSub (check connected?),