Live_component with self updates on phx-update="stream"

Hey! I have the following use-case: a list of messages that is being displayed in the UI and using phx-update=“stream” to optimize changes on updates.

Each message is its own live_component that handles a “streaming-like” effect using send_update_after with the chunk of the message I want to update on the UI.

I also have a phx-hook that contains a MutationObserver to check on a specific custom data attribute data-we-streaming. Everytime there’s a change on this data attribute or in the element childList, an event is pushed to the live_view to inform it if there’s any element streaming at the moment.

Code-wise it looks something like this:

<div phx-update="stream" phx-hook="streamingController">
   <%= for {dom_id, message} <- @streams.ui_messages do %>
     <.live_component module={MyLiveComponent} message={message} />
   <% end %>
</div>

And the hook:

const hook: Hook<StreamingControllerHook> & StreamingControllerHook = {

  mounted() {
    this.pushEvent('streaming-status', { isStreaming : !!this.el.querySelector('[data-we-streaming]') });

    // Create a MutationObserver to watch for DOM changes
    this.observer = new MutationObserver((mutations) => {
      const shouldPushEvent = mutations.some((mutation) => mutation.type === 'attributes' || mutation.type === 'childList');

      if (shouldPushEvent) {
        this.pushEvent('streaming-status', { isStreaming : !!this.el.querySelector('[data-we-streaming]') });
      }
    });

    // Start observing the entire document for changes
    this.observer.observe(this.el, {
      childList       : true,
      subtree         : true,
      attributes      : true,
      attributeFilter : ['data-we-streaming'],
    });
  },

  updated() {
    this.pushEvent('streaming-status', { isStreaming : !!this.el.querySelector('[data-we-streaming]') });
  },

  destroyed() {
    // Clean up the observer when the hook is destroyed
    if (this.observer) {
      this.observer.disconnect();
    }
  },
};

It all was working fine until I pushed this code to PRD: sometimes and in some browsers, the fake streaming behavior just freezes and the user gets stuck (since there is some conditional logic for the user to progress ONLY when no element is streaming).

My question is: can this be caused by the existence of a live_component inside of an element that contains a stream strategy? Or theoretically there shouldn’t exist any blockers with this approach?

Thanks for your time,
Sara

Have you omitted setting the dom_id in your component?

id={dom_id}

From the docs https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html:

Required DOM attributes
For stream items to be trackable on the client, the following requirements must be met:

The parent DOM container must include a phx-update="stream" attribute, along with a unique DOM id.
Each stream item must include its DOM id on the item's element.


Note
Failing to place phx-update="stream" on the immediate parent for each stream will result in broken behavior.

Also, do not alter the generated DOM ids, e.g., by prefixing them. Doing so will result in broken behavior.

When consuming a stream in a template, the DOM id and item is passed as a tuple, allowing convenient inclusion of the DOM id for each item. For example:

<table>
  <tbody id="songs" phx-update="stream">
    <tr
      :for={{dom_id, song} <- @streams.songs}
      id={dom_id}
    >
      <td>{song.title}</td>
      <td>{song.duration}</td>
    </tr>
  </tbody>
</table>


1 Like