Is this the best way to handle server-side session data in Phoenix Liveview?

Just to clarify: Do you want to persist the selections across requests or across LiveViews? That is, do you want to write the selections into the user-side or server-side session?

pentacent.com describes the difference very well in this blog article:

For user-side sessions, all session data is signed and sent to the user as a cookie. On their next request, this signed cookie is sent back to the server and decoded. Server-side sessions only store a session ID in a cookie and store the associated data in a database or in server memory.

If you want to store the selections on the user-side (in the session cookie), then you need to use phx-trigger-action or another JavaScript workaround in order to call a Phoenix.Controller action to put the value into the cookie. My understanding is that this workaround is necessary since you can’t change the user’s cookies through a Websocket, but only through a XHR Request. Since the LiveView only has the Websocket connection and closes the XHR Request connection after the every (of the two) successful mount, you can’t put something into the user-side session from within your LiveView. Here’s a thread with more intelligent answers, which explains this better: How to manage session state with live view

If you want to keep a server-side session though and share it between multiple LiveViews, then the phoenix_live_session-library might be your best option unless you want to code something yourself. However, it isn’t the only option available to you. You could also use:

  1. A GenServer for every user_id, which holds the selection state and notifies the LiveViews to which the user is connected about any state changes.
  2. In combination with 1., you can use Phoenix.PubSub for updating any connected LiveViews. So, if a user has two tabs open and changes the selection in one tab, the first LiveView would update the GenServer state, which would publish a state_changed event to the second LiveView, which then fetches the latest state and updates itself.
  3. Consider replacing 1. with an ETS-table or an Agent. As I understand it, phoenix_live_session uses an ETS-table under the hood. You won’t have to worry about the lifecycle of the GenServer in 1. if you opt for an ETS-table, which might be nice. However, you have to make sure to delete the ETS-table when the last LiveView connected to the user terminates. This might become a bit messy.
  4. Use Phoenix.PubSub exclusively without a GenServer or an ETS-table. When the first LiveView connects, it can publish a maybe_get_state-message and wait for a response. If no response arrives, it will simply set the default state (i.e. no selection made). Whenever another LiveView connects, the first LiveView will answer to the new LiveView with the current state. This way you have no state and you don’t need to worry about cleaning up after the last LiveView of the user terminates. However, managing the messages between LiveViews might become messy. Like: Who answers to new LiveViews? Everybody or only the first/leader LiveView? What if the leader is no longer around (i.e. that tab was closed but others are still open). How do you handle the case when two LiveViews have different state? This is a textbook case in which eventual consistency might come in handy, but it would be WAY overkill for your use-case though :smiley:
7 Likes