--- name: phoenix-liveview description: Use when working with Phoenix LiveView. Covers lifecycle, mount/handle_event/handle_info callbacks, file uploads, navigation, PubSub, and LiveView testing. --- # Phoenix LiveView Patterns ## LiveView Lifecycle A LiveView goes through two phases: 1. **Static Mount**: Initial HTTP request (connected?: false) 2. **Connected Mount**: WebSocket upgrade (connected?: true) ```elixir def mount(_params, _session, socket) do if connected?(socket) do # Subscribe to topics, start timers, etc. Phoenix.PubSub.subscribe(MyApp.PubSub, "topic") end {:ok, assign(socket, :data, [])} end ``` ## Handle Event Use pattern matching in `handle_event/3` for different actions. ```elixir def handle_event("save", %{"post" => post_params}, socket) do case Posts.create_post(post_params) do {:ok, post} -> {:noreply, socket |> put_flash(:info, "Created!") |> assign(:post, post)} {:error, changeset} -> {:noreply, assign(socket, :changeset, changeset)} end end def handle_event("delete", %{"id" => id}, socket) do Posts.delete_post(id) {:noreply, assign(socket, :posts, Posts.list_posts())} end ``` ## Handle Info Handle async messages and PubSub broadcasts with `handle_info/2`. ```elixir def handle_info({:post_created, post}, socket) do {:noreply, update(socket, :posts, fn posts -> [post | posts] end)} end def handle_info(%{event: "presence_diff"}, socket) do {:noreply, assign(socket, :online_users, get_presence_count())} end ``` ## Socket Assigns Use `assign/2` or `assign/3` to update socket state. ```elixir # Single assign socket = assign(socket, :count, 0) # Multiple assigns socket = assign(socket, count: 0, name: "User", active: true) # Update existing assign socket = update(socket, :count, &(&1 + 1)) ``` ## Temporary Assigns Use temporary assigns for large collections that don't need to persist. ```elixir def mount(_params, _session, socket) do socket = assign(socket, :posts, []) {:ok, socket, temporary_assigns: [posts: []]} end ``` ## LiveView Uploads Use built-in upload functionality for file uploads. ```elixir def mount(_params, _session, socket) do socket = socket |> assign(:uploaded_files, []) |> allow_upload(:image, accept: ~w(.jpg .jpeg .png), max_entries: 5, max_file_size: 10_000_000 ) {:ok, socket} end def handle_event("validate", _params, socket) do {:noreply, socket} end def handle_event("save", _params, socket) do uploaded_files = consume_uploaded_entries(socket, :image, fn %{path: path}, entry -> dest = Path.join("priv/static/uploads", entry.client_name) File.cp!(path, dest) {:ok, "/uploads/#{entry.client_name}"} end) {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))} end ``` ## Flash Messages Use `put_flash/3` and `clear_flash/2` for user feedback. ```elixir def handle_event("save", params, socket) do case save_data(params) do {:ok, _} -> socket = put_flash(socket, :info, "Saved successfully!") {:noreply, socket} {:error, _} -> socket = put_flash(socket, :error, "Failed to save") {:noreply, socket} end end ``` ## Live Navigation Use `push_navigate/2` or `push_patch/2` for navigation. ```elixir # Full page reload (new LiveView) {:noreply, push_navigate(socket, to: ~p"/users")} # Patch (same LiveView, different params) {:noreply, push_patch(socket, to: ~p"/posts/#{post}")} ``` ## Handle Params Use `handle_params/3` to respond to URL changes. ```elixir def handle_params(%{"id" => id}, _uri, socket) do post = Posts.get_post!(id) {:noreply, assign(socket, :post, post)} end def handle_params(_params, _uri, socket) do {:noreply, socket} end ``` ## Streams Use streams for efficient rendering of large lists. ```elixir def mount(_params, _session, socket) do {:ok, stream(socket, :posts, Posts.list_posts())} end def handle_event("add", %{"post" => attrs}, socket) do {:ok, post} = Posts.create_post(attrs) {:noreply, stream_insert(socket, :posts, post, at: 0)} end def handle_event("delete", %{"id" => id}, socket) do Posts.delete_post(id) {:noreply, stream_delete_by_dom_id(socket, :posts, "posts-#{id}")} end ``` ## Components Extract reusable UI into function components. ```elixir def card(assigns) do ~H"""
<%= @content %>