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

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

## Listing our users

Before it's possible for users to create matches between themselves they must at least be able to know who else is on the platform. For this, we are going to build a user list page.

## Creating `Accounts.list_users`

Now you should be aware how to add a new context function so let's speedrun through it. Head out to `accounts.ex` and add this function:

```elixir
@doc """
List users.

## Examples

    iex> list_users()
    [%User{}]

"""
def list_users() do
  Repo.all(User)
end
```

Unlike the previous function we created this time we are using [Repo.all/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2) to list all entries on the table corresponding to the `User` model. That should be enough for now. Let's add a new unit test to `account_test.exs`:

```elixir
describe "list_users/0" do
  test "show all users on our system" do
    user = user_fixture()
    assert [^user] = Accounts.list_users()
  end
end
```

The cool thing about the code above is that the assertion above just says 'create an user then listing users should return that exact same user' using the amazing [pin operator](https://elixir-lang.org/getting-started/pattern-matching.html#the-pin-operator) (`^`). In case you never seen it in action, definitely take a look later. This should be enough for our context for now.

## Creating your first new route

So far you got new routes for free using generators. Time to do it by hand. The first thing you want to check is your `router.ex` file under `champions_web`. There's a ton of new things here but to start simple, look for `live_session :current_user`, and we are going to put our new route there.

```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
end
```

Since we will be creating a LiveView we used the [live/4](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Router.html#live/4) macro. The arguments we pass are the route path name, the LiveView file name and the live action. Since the LiveView we passed is called `UserLive.Index` that means we must define a module called `ChampionsWeb.UserLive.Index`. As for the live action it could be any atom but it's recommended to use `:index`, `:show`, `:edit`, `:new`, `:create` and `:update` whenever possible. We will take a look on them over the project's progress.

## Creating the your first LivView

Create the `lib/champions_web/live/user_live` folder then make a file called `index.ex`. Let's start by creating your first LiveView ever.

```elixir
defmodule ChampionsWeb.UserLive.Index do
  use ChampionsWeb, :live_view

  @impl true
  def render(assigns) do
    ~H"""
    <div>Hello LiveView</div>
    """
  end
end
```

This is the minimal LiveView module you can create to show a Hello World. First of all, the module name is important: it should match what you defined inside `router.ex` to the letter prepended by `ChampionsWeb`. The second line contains the `use` macro to import useful things for LiveView modules defined under `ChampionsWeb`. If you're curious open `champions_web.ex` and look for `def live_view do`, we will talk about this at another time.

The real start of the module above really is the `render/1` function. All LiveViews must have a render function that takes exactly one argument called `assigns`. Those functions often return a HEEx markup delimited by the `H sigil` (often wrote as `~H"""`). This time it only returns a single div. Open [http://localhost:4000/users](http://localhost:4000/users) to see this working.

## Giving the page a title

LiveViews separate state management from view rendering. The rule of thumb is that your render function should only know how to render whatever it is in your assigns and the other LiveView functions, known as callbacks, are responsible of managing the state you need.

To get started let's meet your first LiveView callback: [mount/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:mount/3). This callback is responsible often for starting the initial state of your view and also is likely to fetch data from the server. Let's start with the simplest scenario.

```elixir
@impl true
def mount(_params, _session, socket) do
  {:ok, socket}
end
```

This `mount/3` callback receives parameters from the URL, the session state and the socket but ignore everything but the socket. All `mount/3` must return a tuple with `:ok` as the first element and a socket as the second argument. Sockets maintain the state of your LiveView. We will talk more about that over time. The current state of that callback is the same as it didn't exist, it does nothing.

If you are on `/users` and look at your tab name you're going to see something generic including the name of your app. Let's improve that. Update your `mount/3` callback to this:

```elixir
@impl true
def mount(_params, _session, socket) do
  {:ok,
    socket
    |> assign(:page_title, "Listing Users")
  }
end
```

Now instead of returning the socket as-is we used [assign/3](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#assign/3) to assign `page_title` to your socket state. Why `page_title`? This assign is used by `root.html.heex` to generate your HTML `<title>` tag. Looking at your tab name should show 'Listing Users · Phoenix Framework' now. You'll notice we will be adding `page_title` assign to most if not all pages from now on.

## Using streams to list users

Recently LiveView added a new memory efficient way of handling collections of data: streams. For the sake of this chapter will overly simplify things and just say streams are used for lists of things. They behave a lot like assigns: you defined them on callbacks and your render function renders them. Let's rewrite out module:

```elixir
defmodule ChampionsWeb.UserLive.Index do
  use ChampionsWeb, :live_view

  alias Champions.Accounts

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
      socket
      |> assign(:page_title, "Listing Users")
      |> stream(:users, Accounts.list_users())
    }
  end

  @impl true
  def render(assigns) do
    ~H"""
    <.header>
      Listing Users
    </.header>

    <.table
      id="users"
      rows={@streams.users}
    >
      <:col :let={{_id, user}} label="Email"><%= user.email %></:col>
      <:col :let={{_id, user}} label="Points"><%= user.points %></:col>
    </.table>
    """
  end
end
```

The first step to create a stream is, just like assigns, add it to the socket. With line 11 we create `@streams.users` containing the list of all users in our platform. Phoenix comes packed with a component called `<.table>` that is aware of streams and can render those just fine. All you need to do is pass two parameters to it: `id` and `rows`.

Inside the `<.table>` we can spot two `<:col>` slots, each representing a single column on our table. In short those part of the `<.table>` component as we used them to render each item from our stream. There's also the `<.header>` component but that's very self explanatory. Your `/users` page should look something like this:

![A simple table listing one user with email lubien@example.com with 0 points.](https://slabstatic.com/prod/uploads/or5adfz5/posts/images/xx2_DEE-QiC3Gg2TDfnyux5C.png)

## Testing your LiveView

It brings me so much joy to say that testing LiveViews are not only easy but support for it comes by default. Since our LiveView file is `lib/champions_web/live/user_live/index.ex` our test file will live in `test/champions_web/live/user_live_test.exs`.

```elixir
defmodule ChampionsWeb.UserLiveTest do
  use ChampionsWeb.ConnCase

  import Phoenix.LiveViewTest
  import Champions.AccountsFixtures

  defp create_user(_) do
    user = user_fixture()
    %{user: user}
  end

  describe "Index" do
    setup [:create_user]

    test "lists all users", %{conn: conn, user: user} do
      {:ok, _index_live, html} = live(conn, ~p"/users")

      assert html =~ "Listing Users"
      assert html =~ user.email
    end
  end
end
```

The whole magic lies in [live/2](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#live/2) from the `Phoenix.LiveViewTest` module. All you need to do is pass a `conn`, which already comes ready for tests that `use ChampionsWeb.ConnCase` and a route such as `~p"/users"`. The return value is a 3-tuple of `:ok`, a reference to the LiveView so we could do things like navigation or clicking buttons, and last but not least the rendered HTML so far. For this test all we need to do is check if the title and HTML contain what we expect such as the user email and the title on our render function.

## Summary

- Using [Repo.all/2](https://hexdocs.pm/ecto/Ecto.Repo.html#c:all/2) its possible to query all items from an Ecto model.
- To add new pages to Phoenix always start with the `router.ex`
- Phoenix routes map to  modules such as `live "/users", UserLive.Index, :index` mapping to `ChampionsWeb.UserLive.Index`
- It's a common practice to call route actions as `:index`, `:new`, `:create`, `:edit`, `:update` and `:delete` but any other atom works such as `:confirm_email`.
- To create a LiveView you start with creating the render function.
- Render functions always take one argument: `assigns`.
- LiveView manages the state via callback functions.
- We can use the [mount/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:mount/3) callback to create the initial state of our LiveView.
- Assigning something to `page_title` change the value of the `<title>` tag because that's used on `root.html.heex`
- Assigns are just variables accessible on the LiveView render function using `@assign_name`.
- Streams are how LiveView handles collections of data in a memory-efficient manner. Usually used for lists.
- Streams are very like assigns: you defined and manage them on callbacks then use them on the render function as `@streams.stream_name`.
- Phoenix comes with helper components such as `<.header>` and `<.table>`.
- The `<.table>` component does all the stream's magic so we can learn that later.
- Testing a LiveView can be quite easy with the [live/2](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#live/2) helper.

[Read chapter 6: Creating the user page](https://dev.to/lubien/the-lazy-programmers-intro-to-liveview-chapter-6-17ef)