Phoenix LiveView

Rich, real-time UIs rendered on the server; the browser receives minimal diffs over a WebSocket.

46.1 A counter LiveView

defmodule BlogWeb.CounterLive do
  use BlogWeb, :live_view

  def mount(_params, _session, socket), do: {:ok, assign(socket, count: 0)}

  def handle_event("inc", _params, socket), do: {:noreply, update(socket, :count, &(&1 + 1))}
  def handle_event("dec", _params, socket), do: {:noreply, update(socket, :count, &(&1 - 1))}

  def render(assigns) do
    ~H"""
    <h1>Count: {@count}</h1>
    <button phx-click="dec">-</button>
    <button phx-click="inc">+</button>
    """
  end
end

46.2 Lifecycle

HTTP request → mount/3 + render/1 → static HTML (SEO-friendly).

Client connects via WebSocket → mount/3 again (connected) → stateful process.

Events (phx-click, phx-submit, phx-change, phx-keydown, phx-blur) → handle_event/3.

handle_params/3 on URL changes (push_patch, <.link patch=...>).

handle_info/2 for PubSub/process messages → real-time updates.

connected?(socket) distinguishes the two mounts (subscribe to PubSub only when connected).

46.3 Forms & validation

def mount(_, _, socket), do: {:ok, assign(socket, form: to_form(Posts.change_post(%Post{})))}

def handle_event("validate", %{"post" => p}, socket) do
  cs = Posts.change_post(%Post{}, p) |> Map.put(:action, :validate)
  {:noreply, assign(socket, form: to_form(cs))}
end

def handle_event("save", %{"post" => p}, socket) do
  case Posts.create_post(p) do
    {:ok, post} -> {:noreply, push_navigate(socket, to: ~p"/posts/#{post}")}
    {:error, cs} -> {:noreply, assign(socket, form: to_form(cs))}
  end
end
<.form for={@form} phx-change="validate" phx-submit="save">
  <.input field={@form[:title]} label="Title" />
  <.button>Save</.button>
</.form>

46.4 Advanced LiveView

Streams (stream/3, stream_insert/3) — huge collections without keeping them in memory.

LiveComponents — stateful sub-components (use Phoenix.LiveComponent).

Async assigns — assign_async/3, start_async/3, <.async_result>.

Uploads — allow_upload/3, live_file_input, direct-to-S3.

JS commands — JS.show/1, JS.toggle/1, JS.push/2 without custom JS.

Hooks — phx-hook for custom client JS.

on_mount hooks for auth across LiveViews; live_session.

Testing with Phoenix.LiveViewTest: live/2, render_click/2, render_submit/2.