Lesson 19 / 25
Phoenix LiveView
Build interactive, server-rendered UIs with LiveView.
Rich interactivity without a JavaScript framework
Phoenix LiveView lets you build interactive, real-time interfaces in Elixir. A LiveView is a process on the server that holds the page's state in assigns. On first request it renders HTML (good for SEO and fast first paint), then the browser opens a WebSocket and the LiveView becomes stateful: user events (phx-click, phx-submit, phx-change) are sent to the server, handled in handle_event/3, the assigns change, and LiveView sends only the minimal diff of the HTML that changed. Key callbacks are mount/3 (initial state), handle_params/3 (URL changes), handle_event/3 (user interaction) and handle_info/2 (messages, such as PubSub broadcasts). Function components and live components structure the UI, streams efficiently render large collections without holding them in memory, and JS commands handle purely client-side effects. Forms integrate with changesets for live validation. LiveView reached version 1.0 in late 2024. Because each connected user has a process, keep assigns small, and use hooks for the occasional custom JavaScript.
How LiveView works
The server holds state, receives events and pushes minimal HTML diffs over a WebSocket.
A live stock counter with a form
mount, handle_event and HEEx with phx bindings.
defmodule ShopWeb.StockLive do
use ShopWeb, :live_view
alias Shop.Inventory
@impl true
def mount(%{"sku" => sku}, _session, socket) do
if connected?(socket), do: Phoenix.PubSub.subscribe(Shop.PubSub, "stock:#{sku}")
{:ok, assign(socket, sku: sku, stock: Inventory.stock(sku), qty: 1)}
end
@impl true
def handle_event("reserve", %{"qty" => qty}, socket) do
qty = String.to_integer(qty)
case Inventory.reserve(socket.assigns.sku, qty) do
{:ok, remaining} -> {:noreply, socket |> assign(stock: remaining) |> put_flash(:info, "Reserved #{qty}")}
{:error, :insufficient} -> {:noreply, put_flash(socket, :error, "Not enough stock")}
end
end
@impl true
def handle_info({:stock_changed, remaining}, socket), do: {:noreply, assign(socket, stock: remaining)}
@impl true
def render(assigns) do
~H"""
<section>
<h1>{@sku}</h1>
<p>In stock: <strong>{@stock}</strong></p>
<form phx-submit="reserve">
<input type="number" name="qty" value={@qty} min="1" />
<button disabled={@stock == 0}>Reserve</button>
</form>
</section>
"""
end
endCurly-brace interpolation in HEEx
Recent LiveView versions interpolate values in HEEx bodies with {@stock}, while older templates use <%= @stock %>. Attributes have always used value={@qty}. Match the style of your Phoenix version.
Quick check: After the initial render, what does LiveView send to the browser when state changes?
- A minimal diff of the changed parts of the HTML over a WebSocket
- A full page reload
- A JSON API response that the client renders
- Nothing
Answer
A minimal diff of the changed parts of the HTML over a WebSocket — LiveView tracks changes and sends compact diffs.