Stream_insert and stream_delete lose other elements

I am trying to implement system notifications mechanism for my project.

To keep it persistent, I’ve added it as a separate liveview in my app.html.heex with a sticky option set to true. For starters, it’s a pretty simple div rendering messages from a stream:

    <div
      id="dropdown-notifications"
      class="hidden absolute right-0 px-3 py-2 mr-10 z-50 my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow w-60"
    >
      <div class="py-3">
        <span class="block text-sm font-semibold text-gray-700">
          Notifications
        </span>
      </div>
      <div class="max-h-96 overflow-y-auto">
        <div :for={{dom_id, notification} <- @streams.notifications} id={dom_id} class="py-2">
          <span>
            <%= notification.message %>
          </span>
          <button
            type="button"
            class="ml-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8"
            phx-click={JS.push("delete_notification", value: %{id: notification.id}) |> hide("##{dom_id}")}>
            <.icon name="hero-x-mark-solid" class="w-5 h-5" />
            </button>
        </div>
      </div>
    </div>

Mount is getting all notifications and putting them into stream, along with a counter:

  def mount(_params, _session, socket) do
    if admin = socket.assigns[:current_admin] do
      Endpoint.subscribe("notifications:admin:#{admin.id}")

      notifications = Notifications.get_admin_notifications(admin.id)

      socket =
        socket
        |> assign(:notification_count, length(notifications))
        |> stream(:notifications, notifications)

      {:ok, socket}
    else
      socket =
        socket
        |> assign(:notification_count, 0)
        |> stream(:notifications, [])

      {:ok, socket}
    end
  end

There is also a pubsub involved to push new notifications to live admin sessions, and here is where the problem occurs. I handle new notification event pretty straightforward:

  def handle_info(%{event: "notification", payload: notification}, socket) do
    {:noreply, prepend_notification(socket, notification)}
  end

  defp prepend_notification(socket, notification) do
    socket
    |> update(:notification_count, &(&1 + 1))
    |> stream_insert(:notifications, notification, at: 0)
  end

But when I test it, what actually happens is, the list of existing notifications is being replaced with a single new one.

Screenshot 2023-12-25 at 04.11.06
Screenshot 2023-12-25 at 04.11.53

Upon page reload, all notifications are displayed as expected.
Similar thing happens when I delete notification with stream_delete – here, nothing is displayed at all until reload.

What can be the cause of this problem?

The parent container of the stream comprehension needs an id and phx-update=“stream”

3 Likes