What's the correct method for multiple paths pointing to the same LiveView route?

I have two paths, “/” and “/keys”, which are both pointing to the same LiveView route “KeyLive.Index, :index”. What’s the correct method for pointing these paths pointing to the same LiveView route?

At the moment, I am doing the following:

  scope "/", KeyVaultWeb do
    pipe_through :browser
    live "/", KeyLive.Index, :index
    live "/keys", KeyLive.Index, :index

This works, but when I click on the Home page (“/”) there is a very brief flash message stating something about “reconnecting” and if I look at the console, a single click seems to trigger the creation of two LiveView sockets - Note the two CONNECTED messages, two CSRF Tokens and two messages related to mount:

[info] GET /
[debug] Processing with KeyVaultWeb.KeyLive.Index.index/2
  Parameters: %{}
  Pipelines: [:browser]
[debug] QUERY OK source="keys" db=0.0ms idle=1291.4ms
SELECT k0."id", k0."key_identifier", k0."recovery_key", k0."inserted_at", k0."updated_at" FROM "keys" AS k0 []
↳ KeyVaultWeb.KeyLive.Index.mount/3, at: lib/key_vault_web/live/key_live/index.ex:9
[info] Sent 200 in 1ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 24µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "GSpwOn1YDQYfGFodNVxlFSAJJhssGyBwmNHvPmUYQNkhllVfxAc-Tsx1", "_live_referer" => "undefined", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[info] CONNECTED TO Phoenix.LiveView.Socket in 24µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "RhB_elUYEA1jO2A9YHdZNj9wKWYrXRAu2tG6x-HR-mQH9GjEg8lPS5Ho", "_live_referer" => "undefined", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[debug] MOUNT KeyVaultWeb.KeyLive.Index
  Parameters: %{}
  Session: %{"_csrf_token" => "td8L-5X_NV1uY03sXHE6xhXA"}

[debug] QUERY OK source="keys" db=0.1ms idle=1633.5ms
SELECT k0."id", k0."key_identifier", k0."recovery_key", k0."inserted_at", k0."updated_at" FROM "keys" AS k0 []
↳ KeyVaultWeb.KeyLive.Index.mount/3, at: lib/key_vault_web/live/key_live/index.ex:9
[debug] Replied in 373µs
[debug] HANDLE PARAMS in KeyVaultWeb.KeyLive.Index
  Parameters: %{}
[debug] Replied in 31µs

I don’t see this behaviour when I click on the second path, “/keys”:

[debug] MOUNT KeyVaultWeb.KeyLive.Index
  Parameters: %{}
  Session: %{"_csrf_token" => "td8L-5X_NV1uY03sXHE6xhXA"}
[debug] QUERY OK source="keys" db=0.1ms idle=190.9ms
SELECT k0."id", k0."key_identifier", k0."recovery_key", k0."inserted_at", k0."updated_at" FROM "keys" AS k0 []
↳ KeyVaultWeb.KeyLive.Index.mount/3, at: lib/key_vault_web/live/key_live/index.ex:9
[debug] Replied in 354µs
[debug] HANDLE PARAMS in KeyVaultWeb.KeyLive.Index
  Parameters: %{}
[debug] Replied in 33µs

I eventually worked this out. There is noting wrong with the the paths listed above. The issue is actually with the links used for navigation.

The link that I was using was a regular HREF:

      <a href="/">
        <img src={~p"/images/logo.svg"} width="36" />
      </a>

This will trigger a new GET request and a new connection to the LiveView as a result. Here you can see the new PID created for the Mount function and the new socket that is created for LiveView when I click the image and reload the home page:

MOUNT: #PID<0.2509.0>
[info] GET /
[debug] Processing with KvWeb.KeyLive.Index.index/2
  Parameters: %{}
  Pipelines: [:browser]
[debug] QUERY OK source="bitlocker_keys" db=0.1ms idle=451.5ms
SELECT b0."id", b0."key_identifier", b0."recovery_key", b0."inserted_at", b0."updated_at" FROM "bitlocker_keys" AS b0 []
↳ KvWeb.KeyLive.Index.mount/3, at: lib/kv_web/live/key_live/index.ex:10
[info] Sent 200 in 1ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 20µs
  Transport: :longpoll
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "agQZf3NLIU8CNipDGx5rACcABQEELyI-RiUOC9vwWCnrwZZtMXRL7dta", "_live_referer" => "undefined", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
MOUNT: #PID<0.2604.0>
[debug] MOUNT KvWeb.KeyLive.Index
  Parameters: %{}
  Session: %{"_csrf_token" => "8mL00rW8UuD1lD1tjXWM3KV_"}
[debug] QUERY OK source="bitlocker_keys" db=0.1ms idle=706.5ms
SELECT b0."id", b0."key_identifier", b0."recovery_key", b0."inserted_at", b0."updated_at" FROM "bitlocker_keys" AS b0 []
↳ KvWeb.KeyLive.Index.mount/3, at: lib/kv_web/live/key_live/index.ex:10
[debug] Replied in 406µs
[debug] HANDLE PARAMS in KvWeb.KeyLive.Index
  Parameters: %{}
[debug] Replied in 34µs

The correct way is to use the LiveView Patch operation instead:

      <.link patch="/">
        <img src={~p"/images/logo.svg"} width="36" />
      </.link>

Patch will load the content in the same LiveView without triggering a full page reload.

Pragmatic Studio (https://pragmaticstudio.com/) have this to say about when to use Patch vs Navigate (which is similar):

When to use patch vs. navigate

When you want to navigate to the same LiveView process, generate a link with the patch={url} attribute. Clicking the link updates the current LiveView’s state without remounting it. This works great when navigating within the same LiveView as we did to show different servers. It’s also a great choice for pagination or changing how a table is sorted while also updating the URL, as we’ll see in future modules.

When you want to navigate to a different LiveView (redirect), generate a link with the navigate={url} attribute. Clicking the link causes the current LiveView process to shut down (losing its state) and a new LiveView process is spun up and mounted using the current layout. And it’s worth noting that if you try to patch across different LiveViews, it automatically falls back to a redirect.

1 Like