How can I reset contents of `temporary_assigns` assigns

Hi :wave:t2: Elixir Commnuity,

TLDR; How can I reset contents of temporary_assigns (replace already sent data with new data)

I am working on a simple app with Phoenix LiveView. A bit of context before I jump to question.
App has a list of items, number of items can grow to 500+ items. I have “Load More” button to load next batch, while keeping old results. So, with LiveView I have two options, either always send all items as part of assigns, or make items a temporary assign (like here), and it will only send new items, and on client side, phoenix will append the items. Right now, temporary assign is working like charm. However, I stumbled upon a problem, where I need to change order of the items, so I need to refresh the whole list. Unfourtunately, that is not easy, as phoenix client expects that all items should be appended to existing items.

I found a way to accomplish it, but it seems a bit hacky, and maybe there is better approach, hence I am asking here.

My current approach is to have additional param, reset, and in my template I set phx-update based on that param, if it is true, I make phx-update="replace", and other times it is phx-update="append", this allows me to reset contents of temporary_assign when reset=true. However, in all my events on LiveView I need to make sure I delete this param, so consecutive responses from server do not replace whatever content was sent. I am thinking is this the only way to do it, maybe there is built in way I could do it, I couldn’t find it in docs.

I use a similar approach, but instead of using a boolean, I set one of the possible values of phx-update directly: ignore, replace, append, prepend. So, in all handle_params and handle_event callbacks that touch my temporary assign, I also set the update value for that specific use case.

Can you give an example where this wouldn’t be flexible enough?

2 Likes

It is flexible, my problem with this approach is that it is more imperative. i need to always handle this case, in every event and handle_pararm, whereas, if there was a possibility to reset the temporary_assign, I could set it only in one place, that is responsible for it. So, I thought maybe there is already a way, that I don’t know about.

But I guess, that’s the only way for now.

Also, the reason, why I didn’t provide param as one of the possible options, I didn’t want to expose it in the url so user can tweak it. Also, I only take the value of reset param into consideration, if it was changed, by comparing socket.assigns[:params] and params (first argument) in handle_params and if it was not changed, I just remove it, so the url stays more clean (that’s another reason why I don’t like this approach, but that’s more a nitpick :slight_smile: )

I misread your first post. I have an additional assign that handles the phx-update value, not an additional url param.

I’ve answered here to a similar question if that helps.

2 Likes

I believe the answer you are looking for is IDs. Everything that LiveView does is based on ID. So for example, you could do this:

<div id="collection-#{@counter}" phx-update="append">
  <%= for item <- @items do %>
    ...
  <% end %>
</div>

Where @counter starts at 0.

This will work with Load More exactly as you described. Now, whenever you want to start again, bump the @counter and you should be good to go!

17 Likes

Oh, this is nice way to use it, didn’t think about it myself. It gives quite good flexibility, thanks for sharing it :slightly_smiling_face:

This is exactly what I need! Thanks.

I actually went one step further, and just added my existing param value as part of the id, which means I don’t have to keep additional assign arround. I was affraid, that id must be unique, and if id was already it should not be reused, but it seems that’s not the case. As long as id is unique and is different it seems to work, so iterating between three ids, based on selected soting works quite well.

I just tried the counter example while trying to a chat log with Phoenix 1.17 rc.

I’m trying to clear a chat log. It works, but it always transfers the last message to the new container.

Here’s my code:

defmodule MyAppWeb.ChatLive do
  # ...
  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(:messages, [])
      |> assign(:message_list_version, 0)
      # |> ...

    {:ok, socket, temporary_assigns: [messages: []]}
  end

  def render(assigns) do
    ~H"""
    <button phx-click="clear-messages">clear messages</button>
    <ul id={"message-list-#{@message_list_version}"} phx-update="append">
      <%= for message <- @messages do %>
        <li><%= message.content %></li>
      <% end %>
    </ul>
    """
  end

  def handle_event("clear-messages", _params, socket) do
    MyApp.Chat.clear_messages(socket.assigns.room) # This broadcasts a `:messages_cleared` event

    {:noreply, socket}
  end

  def handle_info(:messages_cleared, socket) do
    socket =
      socket
      |> assign(:messages, []) # This doesn't actually do anything afaik
      |> update(:message_list_counter, &(&1 + 1))
      |> put_flash(:info, "Messages cleared")

    {:noreply, socket}
  end
end

I’ve tried to clear it with JS but of course that doesn’t work since I can’t phx-update="ignore" it.

I’ve been banging my head on this one for the past almost hour trying various things and getting a bit sick of it. I’ve looked around here for answers and this is the most satisfactory one I’ve found.

Thanks!

Ok… so this is weird.

I can’t remember why, but for this one list I was using the original comprehension style because the new :for was giving me trouble (which I’m using for every other list).

I just switched it to the new style and that seems to have fixed it… :thinking:

<ul id={"message-list-#{@message_list_version}"} phx-update="append" :for={message <- @messages}>
  <li><%= message.content %></li>
</ul>

I know 1.17 isn’t stable yet but I’m very curious about this!

You have your comprehension on the ul element and not on the li. I think it’s not what you want.

Try moving the comprehension to the li and adding an id to it. Your previous example didn’t have it.

Oh my, ha! I clearly didn’t rtfm for :for very well. Now that I’ve fixed that, my original problem is back, but since I’m already keeping a message count, I’ve solved it by adding:

<ul :if={@message_count > 0}>

I’m still curious as to why the other solution wasn’t working but I’m good with this.

PS, thanks!!