Creating the user page
Now that we have a way to list users in our application we should add a page that shows just one of them so later we can add features such as 'request match'. This chapter be very similar to the previous one with very few additions, you will be able to exercise more of the basics of LiveView that way.
Creating the route
live_session :current_user,
on_mount: [{ChampionsWeb.UserAuth, :mount_current_user}] do
live "/users/confirm/:token", UserConfirmationLive, :edit
live "/users/confirm", UserConfirmationInstructionsLive, :new
live "/users", UserLive.Index, :index
+ live "/users/:id", UserLive.Show, :show
end
Back to the router.ex
add a new route just beside your UserLive.Index
. This time, since we are talking about a single user, we are going to call the module UserLive.Show
using the :show
action. If you pay attention to the URL you're going to see a variable there: :id
. Phoenix calls those variables in the URL params
.
Using params for the first time
Inside lib/champions_web/live/user_live/show.ex
paste the following code:
defmodule ChampionsWeb.UserLive.Show do
use ChampionsWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
@impl true
def handle_params(_params, _session, socket) do
{:noreply,
socket
|> assign(:page_title, "Show user")}
end
@impl true
def render(assigns) do
~H"""
<div>I'm a user</div>
"""
end
end
This time we are looking at handle_params/3 a new LiveView callback. This callback is specifically meant to handle your LiveView params both when your view starts but also when those params change as we will be seeing in later chapters.
In short and omitting some cool details for now, that's the cycle of callbacks before your view is first rendered. Whenever you need to use params, you should prefer to use handle_params
. One thing worth noting here: our mount/3
does absolutely nothing, it just returns {:ok, socket}
so it's fine to delete it. How can we get the id
from params then?
defmodule ChampionsWeb.UserLive.Show do
use ChampionsWeb, :live_view
- @impl true
- def mount(_params, _session, socket) do
- {:ok, socket}
- end
@impl true
- def handle_params(_params, _session, socket) do
+ def handle_params(%{"id" => id}, _session, socket) do
{:noreply,
socket
- |> assign(:page_title, "Show user")}
+ |> assign(:page_title, "Showing user #{id}")
+ |> assign(:id, id)}
end
@impl true
def render(assigns) do
~H"""
- <div>I'm a user</div>
+ <div>I'm the user <%= @id %></div>
"""
end
end
Instead of ignoring our params
using the _
operator at the start of its name we changed the first argument of handle_params/3
to receive a Map that contains the key "id"
and pattern matched it out. Now we have a variable id
so we used that to update the page_title
assign to be more specific and even created a new assign called id
to be used on our render function as @id
. So far we haven't actually fetched the user though.
defmodule ChampionsWeb.UserLive.Show do
use ChampionsWeb, :live_view
+ alias Champions.Accounts
@impl true
def handle_params(%{"id" => id}, _session, socket) do
+ user = Accounts.get_user!(id)
{:noreply,
socket
- |> assign(:page_title, "Showing user #{id}")
+ |> assign(:page_title, "Showing user #{user.email}")
- |> assign(:id, id)}
+ |> assign(:user, user)}
end
@impl true
def render(assigns) do
~H"""
- <div>I'm the user <%= @id %></div>
+ <div>I'm the user <%= @user.email %></div>
"""
end
end
The first thing we needed to do was an alias to Champions.Accounts
so we won't need to write Champions.
when we use the Accounts
context. After that we used Accounts.get_user!/1
, a function created by Phoenix auth generator. We then updated our page_title
to be more descriptive and changed from using an id
assignment to an user
assignment. Let's make this page slightly prettier. Change your render function to this:
@impl true
def render(assigns) do
~H"""
<.header>
User <%= @user.id %>
<:subtitle>This is a player on this app.</:subtitle>
</.header>
<.list>
<:item title="Email"><%= @user.email %></:item>
<:item title="Points"><%= @user.points %></:item>
</.list>
<.back navigate={~p"/users"}>Back to users</.back>
"""
end
You already know about the <.header>
component albeit you're seeing how to use <:subtitle>
inside it now but there are also two new components here for you and both come from Phoenix by default too. The first one is <.list>
which is kinda like a horizontal table but instead of each row being an element on a list we use manually add each row using <:item>
. Also, another fun component to use is <.back>
which will simulate a back button press to the appointed path passed to it's navigate
property.
Oh no, we just realized we have a way from going from /users/:id
to /users
but we don't have the other way around. Our users are in trouble. Let's go back to lib/champions_web/live/user_live/index.ex
:
@impl true
def render(assigns) do
~H"""
<.header>
Listing Users
</.header>
<.table
id="users"
rows={@streams.users}
+ row_click={fn {_id, user} -> JS.navigate(~p"/users/#{user}") end}
>
<:col :let={{_id, user}} label="Email"><%= user.email %></:col>
<:col :let={{_id, user}} label="Points"><%= user.points %></:col>
</.table>
"""
end
Phoenix got you covered. The <.table>
component can receive an row_click
attribute that receives a function that takes a single argument {id, element}
and you can use JS.navigate/1 to change into the user page.
Adding tests!
Let's get back to test/champions_web/live/user_live_test.exs
and add a new test suite:
describe "Show" do
setup [:create_user]
test "displays user", %{conn: conn, user: user} do
{:ok, _show_live, html} = live(conn, ~p"/users/#{user}")
assert html =~ "This is a player on this app"
assert html =~ user.email
end
end
Nothing really magical here, we just go straight to the page and verify that the information that should be there is there.
Summary
- Phoenix routes can receive variables using the
:param_name
syntax inside it's URL. - Params are passed to LiveViews inside both
mount/3
andhandle_params/3
. They are run in that order before our render function. - Devs should prefer using params on
handle_params/3
because it's aware of when params change in some specific cases we will dive in later. - Phoenix comes with a
<.list>
component for horizontal lists and<.back>
for navigating back through pages. - The
<.table>
component can receive anrow_click
attribute to handle clicks on rows. We used that to navigate from the users list to the user page.
Top comments (0)