Search code examples
elixirphoenix-live-view

How to append only new items in html/heex from a RSS feed?


In the mount method, I call an RSS feed for the first time and render all results it returns. In the handle_info method, I perform a Process.send_after call to retrieve that same RSS feed every 5 seconds. The RSS feed contains the original items returned during mount but it will also include new items not returned during mount.

With this implementation, the list rendered in the initial mount just gets overridden with the items from handle_info. How can I append only the new items in the html/heex and retain the existing items so the UI shows a growing list of items?

Edit:

Ideally, the handle_info method will return a LiveView stream but stream items are temporary and freed from socket state immediately after the render/1 function is invoked Due to this, there is no reference to the previous retrieved RSS feed items and it cannot be determined which RSS items have been rendered on the client and which RSS items are considered "new" to the client.

If handle_info returns an assign, access to the previous rendered items are available in the socket so new items can be determined. But the large number of records will cause issues and the new items are appended to the end of the rendered list. Users now have to keep scrolling to the bottom of the page to see the new items.

Any suggestions on how a stream can access previously returned data or how an assign can prepend new items to the top of the page without scrolling is greatly appreciated.


Solution

  • Without much context, I believe you can find the answer in the basic example of the LiveView docs.

    You need to keep the RSS feed items in the assigns of your LiveView. You can set the inital items and schedule the first update during mount

    def mount(_params, _session, socket) do
      if connected?(socket), do: Process.send_after(self(), :update_from_feed, 5_000)
      
      items = call_rss_feed()
      {:ok, assign(socket, :rss_items, items}
    end
    

    and update the assigns accordingly in the handle_info:

    def handle_info(:update_from_feed, socket) do
      new_items = call_rss_feed()
    
      # here you need to add the logic of merging socket.assigns.items and new_items
      items = ...
    
      {:noreply, assign(socket, :items, items)}
    end
    

    EDIT (as more context was added to the question):

    As the usage of stream is preferred, I see two possibilities:

    1. Set the :reset option of stream/4 to true and always pass the full list of retrieved RSS feed items. Additionally, the items could be ordered from newest to oldest and an appropriate :limit could be set to the stream.

    2. Keep track of the retrieved RSS feed items in the LiveView assigns and implement logic to filter only new items based on what was retrieved earlier. The you can just pass the new items to the stream with stream(socket, :rss_items, new_items, at: 0) as you want to prepend them. Setting a limit might still be useful.