The Lazy Programmer's Intro to LiveView: Chapter 6
Tags: phoenix, liveview, elixir
Published: 2023-06-18 15:07:39

[Go to Chapter 1](https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-1-1487)

## 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

```diff
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:

```elixir
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](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c: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.

![](https://slabstatic.com/prod/uploads/or5adfz5/posts/images/rB5iREN12_QRqlzkGLHkl8o_.png)

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?

```diff
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.

```diff
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:

```elixir
@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`:

```diff
@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](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#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:

```elixir
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` and `handle_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 an `row_click` attribute to handle clicks on rows. We used that to navigate from the users list to the user page.

[Read chapter 7: Conceding points to the winner](https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-7-47e0)