### Berenice Medel

One of the most important challenges when developing a new website is to give the user a great navigation experience. The user must know where they are and what navigation options they have at their disposal within the website. For this, we use navigation components such as navbars or sidebars, and developers face the challenge of showing the user their current position in a clear and intuitive way.

Today I’ll share a recipe for handling navigation within our Liveview applications, and how we can offer a navigation bar that shows the active tab the user is currently on.

## Defining the sidebar markup

First, we’ll define the markup of the application sidebar within our live layout in `live.html.heex`, which will be part of the lifecycle of our LiveView components.

```elixir
<%= if @current_user do %>
  <.sidebar_nav current_user={@current_user} active_tab={@active_tab}/>
<% end %>
```

Let’s take a closer look at the previous code; there are two assigns defined, `@active_tab` and `@current_user`. We are passing these as parameters to a function component called `sidebar_nav_links`, defined in the `LiveBeatsWeb.LayoutView` module as follows:

```elixir
def sidebar_nav(assigns) do
  ~H"""
  <nav class="px-3 mt-6 space-y-1">
    <%= if @current_user do %>
      <.link
        navigate={profile_path(@current_user)}
        class={"#{if @active_tab == :profile, do: "bg-gray-200", else: "hover:bg-gray-50"}"}>
        <.icon name={:music_note} outlined />
        My Songs
      </.link>

<.link
        navigate={Routes.settings_path(Endpoint, :edit)}
        class={"#{if @active_tab == :settings, do: "bg-gray-200", else: "hover:bg-gray-50"}"}>
        <.icon name={:adjustments} outlined/>
        Settings
      </.link>
    <% end %>
  </nav>
  """
end
```

We are using the `sigil_H` that returns a rendered structure containing two links: one for _My Songs_ and another for _Settings_. Each link has a CSS class defined depending on the value of `@active_tab`.

```elixir
"#{if @active_tab == :profile, do: "bg-gray-200", else: "hover:bg-gray-50"}"
```

This way, the user perceives their current location within our navigation options.

## Setting the @active_tab assign

We have defined a list of `active_tabs` to assign a conditional CSS class to indicate the user's location within our application. Now we need to see how the `active_tab` and `current_user` assigns are determined. We also need this assign available in all LiveViews since it is rendered in the live layout. Luckily, LiveView lifecycle hooks make this easy.

Let’s take a look at the following code inside `router.ex`:

```elixir
live_session :authenticated, on_mount: [
  {LiveBeatsWeb.UserAuth, :ensure_authenticated},
  LiveBeatsWeb.Nav
] do
  live "/:profile_username/songs/new", ProfileLive, :new
  live "/:profile_username", ProfileLive, :index
  live "/profile/settings", SettingsLive, :edit
end
```

We see that a `live_session` is defined as `:authenticated`, which includes a user validation process. If the user is valid, the content will be shown; otherwise, they will be redirected to the login page.

The first hook handles user validation through the `on_mount` callback of the `LiveBeatsWeb.UserAuth` module:

```elixir
 def on_mount(:ensure_authenticated, _params, session, socket) do
    case session do
      %{"user_id" => user_id} ->
        new_socket = LiveView.assign_new(socket, :current_user, fn ->
            Accounts.get_user!(user_id)
          end)
        {:cont, new_socket}
      %{} ->
        {:halt, redirect_require_login(socket)}
    end
  rescue
    Ecto.NoResultsError -> {:halt, redirect_require_login(socket)}
  end
```

In line 4, `current_user` is assigned to the socket assigns if the current user is found. If not, they are redirected to the log-in view.

The second hook sets the assigns needed to show the active tab in the sidebar.

```elixir
defmodule LiveBeatsWeb.Nav do
  import Phoenix.LiveView

def on_mount(:default, _params, _session, socket) do
    {:cont,
     socket
     |> attach_hook(:active_tab, :handle_params, &set_active_tab/3)}
  end

defp set_active_tab(params, _url, socket) do
    active_tab =
      case {socket.view, socket.assigns.live_action} do
        {ProfileLive, _} ->
          if params["profile_username"] == current_user(socket) do
            :profile
          end

{SettingsLive, _} ->
          :settings

{_, _} ->
          nil
      end

{:cont, assign(socket, active_tab: active_tab)}
  end

defp current_user(socket) do
    socket.assigns.current_user[:username]
  end
end
```

In line 4, we define the `on_mount` callback that will be used as `:default`. The important part of this callback can be seen in line 7, where we call `attach_hook/4`. We named our hook `:active_tab` and attached it to the `handle_params` stage of the socket lifecycle, invoking our `set_active_tab/3` function.

In `set_active_tab/3`, logic is implemented to set `@active_tab` based on parameters, the LiveView module, and live action from the router. This allows the user to navigate through LiveViews, with the tabs highlighting appropriately as the URL changes.
