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

I am struggling with how to store session data in Phoenix Liveview.

Usecase: User will be presented with buttons (up to 10) that allow them to narrow a search on resources. Every time they press a button, the resource table will be updated to reflect the resource_type that they selected. If they turn the button “off”, then that resource_type will no longer be used to narrow the search. So I need to store the selections in session data so that it doesn’t get lost every time I mount the Liveviews.

Is this the best way to tackle this:

Or is there an easier way?

Have you looked into phx-trigger-action?

1 Like

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

Thank you again! I’m going to look into phx-trigger-action. The problem is that I need to keep track of all of the buttons they have been selected (or de-selected) throughout their session because those will drive the query on the database. So if the user has already selected “book” and “blog”, then I need those stored somewhere. When they then select “online video”, I would add that to the list of types to search on.

I don’t want to force the user to login in order to do searches, so I won’t have a user_id. I think that also rules out client side cookies. I checked out the ETS table solution, but the Phoenix documentation warned against using this for the same issue that you brought up: “We don’t recommend using this store in production as every session will be stored in ETS and never cleaned until you create a task responsible for cleaning up old entries.”

It seems odd that there isn’t a standard way to manage session data without login. As Pentacent points out, users will often collect products in their cart before they login. That state needs to be stored somewhere temporarily. It feels like it would be most efficiently stored on the server side. Pentacent mentions that he prefers server side session management as well. I guess his solution might be the best one. It sounds like it is the ETS solution but he has developed a way to manage table clean-up when the session ends. I’ll study it more to try to understand what is going on under the hood. More reading! Thanks again!

1 Like

If all you want to do is to make sure user selections are not lost due to the unfortunate event of liveView disconnect and reconnect, you can use a form with a phx-change handler. You don’t need to submit the form but once the liveview reconnect the form data will be resent to the phx-change handler and you can pick up where you left off the last time.

1 Like