|
1 | | -# Phoenix & LiveView Rules |
2 | | - |
3 | | -## HEEx Template Syntax (Critical) |
4 | | -- **Attributes use `{}`**: `<div id={@id}>` — never `<%= %>` in attributes |
5 | | -- **Body values use `{}`**: `{@value}` — use `<%= %>` only for blocks (if/for/cond) |
6 | | -- **Class lists require `[]`**: `class={["base", @flag && "active"]}` — bare `{}` is invalid |
7 | | -- **No `else if`**: Use `cond` for multiple conditions |
8 | | -- **Comments**: `<%!-- comment --%>` |
9 | | -- **Literal curlies**: Use `phx-no-curly-interpolation` on parent tag |
10 | | - |
11 | | -## Phoenix v1.8 |
12 | | -- Wrap templates with `<Layouts.app flash={@flash}>` (already aliased) |
13 | | -- `current_scope` errors → move routes to proper `live_session`, pass to Layouts.app |
14 | | -- `<.flash_group>` only in layouts.ex |
15 | | -- Use `<.icon name="hero-x-mark">` for icons, `<.input>` for form fields |
16 | | - |
17 | | -## LiveView |
18 | | -- Use `<.link navigate={}>` / `push_navigate`, not deprecated `live_redirect` |
19 | | -- Hooks with own DOM need `phx-update="ignore"` |
20 | | -- Avoid LiveComponents unless necessary |
21 | | -- No inline `<script>` tags — use assets/js/app.js |
22 | | - |
23 | | -## Streams (Always use for collections) |
24 | | -```elixir |
25 | | -stream(socket, :items, items) # append |
26 | | -stream(socket, :items, items, at: -1) # prepend |
27 | | -stream(socket, :items, items, reset: true) # filter/refresh |
28 | | -``` |
29 | | -Template: `<div id="items" phx-update="stream">` with `:for={{id, item} <- @streams.items}` |
30 | | -- Streams aren't enumerable — refetch + reset to filter |
31 | | -- Empty states: `<div class="hidden only:block">Empty</div>` as sibling |
32 | | - |
33 | | -## Forms |
34 | | -```elixir |
35 | | -# LiveView: always use to_form |
36 | | -assign(socket, form: to_form(changeset)) |
37 | | -``` |
38 | | -```heex |
39 | | -<%!-- Template: always @form, never @changeset --%> |
40 | | -<.form for={@form} id="my-form" phx-submit="save"> |
41 | | - <.input field={@form[:name]} type="text" /> |
42 | | -</.form> |
43 | | -``` |
44 | | -- Never `<.form let={f}>` or `<.form for={@changeset}>` |
45 | | - |
46 | | -## Router |
47 | | -- Scope alias is auto-prefixed: `scope "/", AppWeb do` → `live "/users", UserLive` = `AppWeb.UserLive` |
48 | | - |
49 | | -## Ecto |
50 | | -- Preload associations accessed in templates |
51 | | -- Use `Ecto.Changeset.get_field/2` to read changeset fields |
52 | | -- Don't cast programmatic fields (user_id) — set explicitly |
53 | | - |
54 | | -## Testing |
55 | | -- Use `has_element?(view, "#my-id")`, not raw HTML matching |
56 | | -- Debug selectors: `LazyHTML.filter(LazyHTML.from_fragment(render(view)), "selector")` |
57 | | - |
0 commit comments