Passing a livestream to a component/live_component issue

Hello,

I’ve created a stream in a parent liveview that I want passed to a live_component. On the parent, when I create the stream:

    socket
    |> stream(:my_stream, [])
    |> tap(fn x -> IO.inspect(x.assigns) end)

The stream is placed under the streams key in the sockets assigns as indicated:

  streams: %{
    __changed__: MapSet.new([:medications]),
    my_stream: %Phoenix.LiveView.LiveStream{
      name: :medications,
      dom_id: #Function<3.112696910/1 in Phoenix.LiveView.LiveStream.new/4>,
      ref: "0",
      inserts: [],
      deletes: [],
      reset?: false,
      consumable?: false
    },
    __configured__: %{},
    __ref__: 1
  },

However, the minute I attempt the assign that stream to the list of assigns for a component or a live component:

Component:

    <.form_step
      form={@current_form}
...
      my_stream={@streams.my_stream}
...
    />

Live Component:

           <.live_component
              module={Web.Live.Common.StreamTest}
              id="med-search-id"
              my_stream={@my_stream}
            />

The stream is now in the assigns as:

  my_stream: %Phoenix.LiveView.LiveStream{
    name: :my_stream,
    dom_id: #Function<3.112696910/1 in Phoenix.LiveView.LiveStream.new/4>,
    ref: "0",
    inserts: [],
    deletes: [],
    reset?: false,
    consumable?: false
  },

This all makes sense to me. After all I’m literally assigning the stream into the assigns.
This issue comes into play when I attempt to call stream_insert/4. The stream is no longer in socket.assigns.stream, it’s in socket.assigns. So the call to stream_insert fails:

** (KeyError) key :streams not found in: %{
  id: "med-search-id",
  __changed__: %{med_search_list: true, med_search_loading: true},
  flash: %{},
  myself: %Phoenix.LiveComponent.CID{cid: 1},
  my_stream: %Phoenix.LiveView.LiveStream{
    name: :my_stream,
    dom_id: #Function<3.112696910/1 in Phoenix.LiveView.LiveStream.new/4>,
    ref: "0",
    inserts: [],
    deletes: [],
    reset?: false,
    consumable?: false
  },

My situation revolves around a live component so my work around is to just assign a regular list in the live_view, and in the live_component declare a stream. BUT since I’m maintaining the sources-of-truth’s in the live_view, I have to update both the stream AND regular list. The regular list being the source of truth for which I can do things to, and the stream for displaying the source of truth (because I can’t access the stream in the parent live view.)

    new_state =
      state
      |> Map.put(:id, Ecto.UUID.generate())

    socket
    |> stream_insert(:my_stream, new_state)
    |> update(:my_list_for_stream_in_live_component, fn states ->
      [new_state | states]
    end)

Seems hacky and probably unnecessary. This this not the intended purpose of streams? Am I missing something trying to get a stream into a component/live component? Should I just keep to the temporary_assigns and phx-update=“append” method for things like this?

Why should a child component be allowed to insert items in a stream defined in a parent? If the parent is the source of truth then the children are not supposed to fiddle with that.

1 Like

Decent point-could be an improvement opportunity as this is probably more of an I’m-in-the-weeds situation architecturally and I think I needed that pointed question to discover the design flaw :duck: