When using Phoenix LiveView, you might need to get the current URL in any part of the application.

A common use-case is a layout where you show a navigation menu highlighting the current section based on the URL path.

To do this in an elegant way, you can start with defining a LiveHooks module which looks like this:

defmodule MyAppWeb.LiveHooks do
  import Phoenix.Component, only: [assign: 3]
  import Phoenix.LiveView, only: [attach_hook: 4]

  # Define a hook called :global
  def on_mount(:global, _params, _session, socket) do
    # Attach a hook on mount which gets the current path
    {:cont,
     socket
     |> attach_hook(:assign_current_path, :handle_params, &assign_current_path/3)}
  end

  defp assign_current_path(_params, url, socket) do
    # Parse the current path
    uri = URI.parse(uri) |> current_path()

    # Assign the current path in the socket
    {:cont,
     socket
     |> assign(:current_path, uri)}
  end

  # Get the current path with the query string is present
  defp current_path(%URI{} = uri) when is_binary(uri.path) and is_binary(uri.query) do
    uri.path <> "?" <> uri.query
  end

  # Get the curretn path if no query string is present
  defp current_path(%URI{:path => path}), do: path
end

Once the module exists, we can attach it to the live_session in on_mount:

live_session :app,
  # Call the global hook on mount
  on_mount: [
    {MyAppWeb.LiveHooks, :global}
  ] do
  live "/", HomeLive.Index, :home
end

After updating the live_session, all the children of that session will now have access to the current path:

{@current_path}

If you want more flexibility, you can also store the complete URL in the assignments. However, since I'm only interested in the path itself with the query string, I'm first parsing the URL and I'm storing only what I really need.