How do I pack a list inside a string so that I can send it as a param in live_redirect?

I’m continuing to struggle with how to pass session data between two LiveViews. LiveView 1 allows for searching for resources (no authentication needed) and LiveView 2 allows user to save resource (which requires authentication).

Users can filter resources based on certain filter criteria. I need to pass the selected filter criteria to LiveView2 and then back to LiveView1 so that the user doesn’t lose the filters they set.

Criteria is a list that looks like this:

[mediums: [1,4,5], topics: [31], res_types: [1], supplies: [1, 3]]

I unsuccessfully tried passing this list in live_redirect as follows:

<span><%= live_redirect "Save", 
   to: Routes.resourceform_save_resource_path(@socket, :save,
   resourceform.id, "<%=@criteria%>" ) %></span>

This failed. So I tried this and it also fails:

<span><%= live_redirect "Save", 
   to: Routes.resourceform_save_resource_path(@socket, :save,
   resourceform.id, @criteria ) %></span>

I can’t send a list as a param in live_redirect. So I need to pack my list into a string that looks like this:

"[mediums: [1], topics: [31], res_types: [1], supplies: []]"

Then I need to unpack it out of the string when it returns to LiveView1 so that I can apply the criteria filters.

How do I put a List inside of a string and then take it out again?

Just putting it in there should not be to hard, just use Kernel.inspect

iex(1)> list = [:thing, stuff: [1], items: [4]] 
[:thing, {:stuff, [1]}, {:items, [4]}]
iex(2)> Kernel.inspect list
"[:thing, {:stuff, [1]}, {:items, [4]}]"

I’m not sure about the best way to get it out though

1 Like

I should also believe that there probably is a better way. Through assigning it or such.

1 Like

It really looks You have chosen a complex solution, where maybe a simple one could do.

In a liveview You absolutely can tell if there is an authentified user or not.

I don’t see why You don’t use some conditional to check if form should be displayed.

And in the save handler, You could do the same as well.

I do have some liveviews acting in a different way depending on user, user roles, or none.

2 Likes

I’ve tried everything. What I really wish I could do is put a component behind the “authenticated” paths in router.ex, but that is not available.

I’ve tried checking to see if the user is authenticated in the SaveComponent and then sending it off to the login page … but it gets completely lost on the return from login and loses all of the assigns. It returns to the parent LiveView (which causes it to mount again) instead returning to the component … and the save never happens. It just logs the user in and goes back to the parent.

I have spent a week trying every option to just get the assigns to be accessible after authentication and they ALWAYS get lost. Has been seriously frustrating. So I finally decided that my only option is to pass the search criteria in the assigns and then just send them back in a live_redirect. But then I slammed into this problem of not being able to send back a list.

The fact that session data gets lost every time you move to another LiveView is really driving me mad. I feel like I’m creating a gigantic kludge just to accomplish something super simple.

I would welcome any suggestions on a better approach. The bottom line is that I have session data that has to be passed down to children components and liveviews and then passed back up to the parent. I guess if packiing the list in and out of a string (total kludge) doesn’t work, then I might have to save these temporary items into a database but that seems like overkill.

What am I missing? There must be a better way to do this but I’m not finding it. Sigh.

I store my client specific data at client side in localStorage then post the whole things to the liveview on monunted or reconnected with pushEvent.

My experience with liveview is that the liveview is an entirely different beast from “dead” view; any effort trying to mix the two is going to be painful.

1 Like

Ugh. That’s basically what I’m discovering. I feel like I’m fighting it every step of the way. I’m trying to figure out if I should just switch to Rails. It means starting all over. I did everything in Phoenix LiveView. Sigh.

So what is the problem? Just have everything in liveview and kiss “dead” view goodbye. You need to forget about concepts like cookies, query strings, etc. You have a server side, and a client side, just push_event back and forth.

What I mean is you are trying to solve your problem the hard way:

  • why do you put your application logic in leex?
  • why do you try to massage something into a string then massage it out?
1 Like

Let’s use the example of a typical shopping cart. A user goes to an online store and starts shopping without logging in. They add things to the cart and then click “checkout” which requires them to log in. All of the items in the cart have to be saved so that they are still in the cart after login.

The issue I’m having is that the data disappears when I move to login because that is a different LiveView. Even if I use handle_event to grab all of their cart selections and store those in “assigns”, I lose those assigns when I move to a new LiveView. To move from one LiveView to another, I must use live_redirect not live_patch. As soon as I mount the LiveView, I only have access to whatever params were passed in via the live_redirect function call. My session and socket are now flushed … so my session data is gone. I have to rebuild it using those params. That’s where I’m struggling. I need session data to persist across LiveViews instead of disappearing. I can’t lose everything that the person put in their cart.

I do not see why You should lose session between live views. It should be available in each mount, like this…

require Logger
def mount(_params, %{"user" => user} = session, socket) do
    Logger.info("#{__MODULE__} mounted with #{inspect(session)}.")
    ...
    {
      :ok, 
      socket
      |> assign(:user, user)
    }
end

Doesn’t a user need to be logged in for that to work? I have data from before they logged in. So there is no user to associate the data with.

I had not seen Logger.info before. I’ll look at that. Does that just log and retrieve temporary data from a file … so similar to using a temporary database entry but using a file instead?

You should not allow people to do tasks that are not allowed. Why guest can enter data?

I want them to be able to search resources without logging in. So they can select various criteria to filter the search – such as certain resource types (book, blog, etc) and certain mediums. So they select these filters and look at the resources. None of those actions require a log in. they are just looking at a table of resources. I only need them to login if they want to save one of the resources to a repository.

But I want them to be able to login, save the resource and return to the table of resources with the filters still applied. So I need to save those filters.

It will be a super frustrating user experience if they return to the table and all of their filters have disappeared … just because they saved one of the resources.

In the handle_params, I manage search, filter, pagination as part of the assign, it can be done without authentication.

  def handle_params(params, _url, socket) do
    if connected?(socket), do: unsubscribe(socket.assigns.resources)

    search = params["search"]
    category_id = maybe_to_int(params["category_id"])
    department_id = maybe_to_int(params["department_id"])

    # Set default order to be DESC.
    order =
      case params["order"] do
        "asc" -> :asc
        _ -> :desc
      end

    page = maybe_to_int(params["page"], 1)

    # filter = build_search_filter(search)
    filter =
      build_filter(
        title: search,
        category_id: category_id,
        department_id: department_id
      )

    send(self(), {:run_query, filter, page, order})

    options = [
      search: search,
      category_id: category_id,
      department_id: department_id,
      order: order
    ]

    socket =
      socket
      |> assign(:search, search)
      |> assign(:order, order)
      |> assign(:loading, true)
      |> assign(:page, page)
      |> assign(:pagination, %{})
      #
      |> assign(:category_id, category_id)
      |> assign(:department_id, department_id)

    {:noreply, socket}
  end

Yes, I totally agree. I’ve got all of that working without authentication. But as soon as the user wants to save a resource, then I must enforce authentication so that they can save to a repository associated with that user.

All of my issues happen after the user selects “Save Resource.” As soon as I have to move them to login, then I have to redirect them. That causes the assigns to disappear. But I need to save those filters that they set when they were not authenticated.

My app is set up very similar. This all works fine for me. Now let’s say your user wanted to save and you needed to authenticate them. Whether you use a LiveView behind the “authenticated” wall in router.ex or you push them to login via the component, they still have to be redirected to a login. They come back to the liveview using a redirect.

The problem is going from login (normal controller) and live view… I see.

But still there are many ways to store those intermediate data.

An example… give a session_id, with the plug I already show You.

Use this key to store data when You leave live view, to go to the login controller… into an external store.

The simplest case would be an Agent, to store data per session_id key.

When You return to live view, retrieve data from the Agent, using the session key.

1 Like

OK … so then my two choices for this really are:

  1. Find a way to pack the session data (all my filters that are in the List described above in the first post) into a string and then pass that as a parameter in the redirect function.
    OR
  2. Temporarily store the session data in a log file or database. Use a user_id if the user has logged in or create a uniuqe ID myself and call it session_id.

Is that correct?

You might not know them yet, but OTP has tools to store data. ETS, GenServer, Agent.

Take a look at what an Agent is here… Agent — Elixir v1.12.0

It is a simple solution.

One more question … can _csrf_token be used as a unique identifier for the session?