Unable to update stream of items from an handle_event

Hi,

I populate my initial list of cameras to display in the update/2 function.

 def update(assigns, socket) do
    {
      :ok,
      socket
      stream(socket, :selected_cameras, Cameras.list_cameras())
    }
  end


That works and the list is rendered like this:

        <div class="grid-basic"
         :for={{id, component} <- @streams.selected_cameras} 
          phx-value-id={component.id}>
          <.small_card
            title={component.name}
        <./small_card>

But if I try to add another camera to the list, I get no response.

  def handle_event("camera_selected",
    %{"_target" => ["camera_selected"], 
    "camera_selected" => camera_id}, socket) do

    camera_id = String.to_integer(camera_id)
    camera = Cameras.get_camera!(camera_id)

    # This does not work
    #socket = assign(socket, selected_cameras: [camera] )

    # Neither does this
     socket = stream(socket, :selected_cameras, Cameras.list_cameras(), reset: true)
    {:noreply,  socket}
  end

What can I do to tell LiveView that I have a new list of items I want it to render?

A couple of things:

  • Do you have phx-update="stream" on a parent container?
  • You aren’t using the dom id provided by the comprehension:
        <div class="grid-basic"
         :for={{id, component} <- @streams.selected_cameras} 
         id={id} <----------- here
         phx-value-id={component.id}>
          <.small_card
            title={component.name}
        <./small_card>

Also, just to be sure: is grid-basic really a grid cell or is that the parent? If it’s the parent the iterator should be on <.small_card>. Just checking, though, as the naming seems odd, apologies if you know that already.

1 Like

Thanks for you quick reply, @sodapopcan! :blush:
I did however mange to figure out what I were doing wrong.
It works when I do this:

  socket = stream_insert(socket, :selected_cameras, camera)

Ah ya, I thought you wanted to replace, but glad you figured it out!

2 Likes

It works when I do this…

Well, not entirely. The selected camera is added to :selected_cameras, but the existing list is not there. I want to prepend this new camera to the list, not replace it.

After debugging a bit, I noticed that the :selected_cameras array is empty within the “add_camera” event handler.
How can I make sure the existing list is present?

This code lives inside my MyAppWeb.WizardLive.Form, which is an LiveComponent.

   def handle_event("add_camera", _params, socket) do
    camera_id = socket.assigns.camera_id_selected
    camera = Cameras.get_camera!(camera_id)

    # selected_cameras are not available in the socket.assigns.
    # I.e. the following line will return an empty list.
    IO.inspect(socket.assigns.streams.selected_cameras, 
label: "BEFORE insert")

    socket = stream_insert(socket, :selected_cameras, camera)

    # After adding a camera here, it is available and rendered in the view.
    # When I add a camera, it replaces what I have added to the list previously.
    IO.inspect(socket.assigns.streams.selected_cameras,
 label: "AFTER insert")

    {:noreply, socket}
  end

The LiveComponent is mounted like this:

defmodule MyAppWeb.WizardLive.Summary.Edit do
  use MyAppWeb, :live_view

  alias MyAppWeb.Wizard.Summary
  alias MyAppWeb.Wizard

  @impl true
  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(:summary, %Summary{})
    {:ok, socket }
  end


  @impl true
  def render(assigns) do
    ~H"""
    <div class="page-container">
        <.live_component module={MyAppWeb.WizardLive.Form} />
      </div>

The whole idea of streams is that the list is deleted freeing up the memory. Hence:

This will make sure inserted stream items don’t make other items be removed.

Hi @LostKobrakai,

Thanks for your suggestion!
I tried this update to the parent element, but I still got the same result.
The streams.selected_cameras is always empty at the start of the handle_event.

<div 
  class="page-container" 
  phx-update="stream"
  id="wizardform">

Here, you can see how I initiated the selected_cameras stream.
However, the assign_selected_cameras/1 is not triggered after I select a camera, so I don’t think that’s the cause of the empty list.

defmodule MyAppWeb.WizardLive.Form do
  use MyAppWeb, :live_component
  import Phoenix.HTML.Form


  @impl true
  def update(assigns, socket) do
    {
      :ok,
      socket
      |> assign_selected_cameras()
    }
  end

  defp assign_selected_cameras(socket) do
    IO.puts "ASSIGN SELECTED CAMERAS"
    stream(socket, :selected_cameras, [])
  end

What do you mean by empty? Do you mean that inside your handle_event(... you can’t see the contents of the stream? Like LostKobrakai mentioned, that’s how streams work. You can use

  • stream: set the contents of the stream. Will add new items to an existing stream, but will not remove items.
  • stream_insert: add / update items
  • stream_delete: remove items
  • stream(..., reset: true) to flush out all previous items and set the list to what you provide

If you need access to the existing cameras inside of your LiveView handle_event functions, you’ll have to switch from using streams to using regular assigns.

If I missed what your problem is, could you start over a bit and explain the flow a bit more? I think I may be confused about what you’re trying to achieve.