Spice up your LiveView app with a cool loading spinner!
Tags: elixir, phoenix, liveview, userexperience
Published: 2023-12-15 15:31:30

---
title: Spice up your LiveView app with a cool loading spinner!
published: true
description: Learn how easy it is to add a spinner when making a long-time request on your LiveView application. 
tags: #elixir, #phoenix, #liveview, #userexperience
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-10-24 17:11 +0000
---

When developing applications, it's common to encounter scenarios where a user's request takes longer than expected. This delay can result from various factors like data transformation, external API calls, or slow database queries.

Fortunately, when constructing a Phoenix app that leverages the capabilities of LiveView, enhancing the user experience by incorporating spinners for such extended requests is a breeze.

The behavior I aim to illustrate is as follows:

- The user encounters the "Load Data" button on the interface.
- Upon clicking, we simulate an extended request by introducing a `:timer.sleep(2000)`.
- While the request loads the necessary data, a spinner becomes visible.

It might seem trivial, but from a user perspective, this serves as valuable feedback, helping them understand that the request might take a couple of seconds to complete.

> :warning: I will not cover the steps to start a new Phoenix LiveView project and setup aspects.

So let's start the coding.

The first step is to implement our LiveView module with the `mount/3` function. This mount function will assign values to two variables in the socket `posts`, which will contain fake data for demonstration purposes, and `loading`, which will control the state of the loading spinner on the interface.

```elixir
# lib/spinner_web/live/spinner_live.ex

defmodule SpinnerWeb.SpinnerLive do
  use SpinnerWeb, :live_view

  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(:posts, [])
      |> assign(:loading, false)

    {:ok, socket}
  end
end
```

For rendering, I've created an `html.heex` template at the same directory level.

```html
<!-- lib/spinner_web/live/spinner_live.html.heex -->

<div>
  <button
    phx-click="load-posts"
    class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:border-blue-300"
  >
    Load Posts
  </button>
</div>
<br /><br />
<%= if @loading do %>
  <svg
    class="animate-spin h-10 w-10 text-blue-500"
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
  >
    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
    </circle>
    <path
      class="opacity-75"
      fill="currentColor"
      d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 6.627 5.373 12 12 12v-4a7.946 7.946 0 01-6-2.709z"
    >
    </path>
  </svg>
<% end %>
```

In the HTML, the crucial components are:

- the button with a `phx-click="load-posts"` event assigned
- the condition using the `loading` variable; this condition, along with the variable, will control when the spinner must appear.

Well, since I've set a `phx-click="load-posts"` event on the button, I need to implement that on the backend.

```elixir
def handle_event("load-posts", _params, socket) do
  socket = assign(socket, :loading, true)

  send(self(), :load_posts)

  {:noreply, socket}
end
```

Here is where the trick happens, In the first line of the function we set the `loading` assigned in the socket to be `true` and spawn a new message by using the function [`send/3`](https://hexdocs.pm/elixir/1.12.3/Process.html#send/3). This function will receive an internal PID and a message, in this case `:load_posts`. Last but not least we return the `{:noreply, socket}` with assigns updated, in this case only the `loading` as true. At this point, the spinner will appear on the interface.

Since we are sending an internal message using `send/3` in the `handle_event/3` callback, we need to capture that in the LiveView module using a new callback, `handle_info/2`.

The new callback will primarily handle three steps:

- Load the data we are expecting (in this case, a fake list of posts)
- Set loading as false to remove the spinner from the interface
- Return the new socket data with `{:noreply, socket}`

```elixir
def handle_info(:load_posts, socket) do
    # Just to emulate the long-time request
    :timer.sleep(2000)

    socket =
      socket
      |> assign(:posts, get_posts())
      |> assign(:loading, false)

    {:noreply, socket}
  end
  
  defp get_posts do
    [
      %Spinner.Post{title: "First Title", description: "First Description"},
      %Spinner.Post{title: "Second Title", description: "Second Description"}
    ]
  end
```

To simulate a case where the request takes a long time to run, I manually add a `:timer.sleep/1` that takes 2 seconds to complete.

After these 2 seconds, the spinner will hide, and the list with loaded posts will appear on the interface. Just need to build the code for that on the HTML page.

```html
<%= unless Enum.empty?(@posts) do %>
  <ul class="mt-4">
    <li :for={post <- @posts}>
      <h2 class="text-lg font-bold"><%= post.title %></h2>
      <p class="text-gray-600"><%= post.description %></p>
    </li>
  </ul>
<% end %>
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9tvoq993oq6hs9vzy73.gif)

Hope that was helpful!


The whole example can be found here: https://github.com/lcezermf/elixir-playground/tree/master/spinner