PubSub.broadcast inside live component ignores topic, calls parent?

Hello!

I am working on building out a simple chat interface using LiveView and live components. My approach was to have each LC use Phoenix.PubSub.subscribe/broadcast, as I was aiming for a simple drop-in component which didn’t require altering the parent LV state/etc.

However, I’ve found that calling Phoenix.PubSub.broadcast/3 from inside the LC seems to ignore the broadcast topic, triggering an event on the parent LV instead.

Here’s a (brief) code snippet to demonstrate:

# my_chat_component.ex
def mount(socket) do
  Phoenix.PubSub.subscribe(Example.PubSub, "chat:id")
  ...
end
def handle_event("send_message", message, socket) do
  Phoenix.PubSub.broadcast(Example.PubSub, "chat:id", {:chat_message, message})
  ...
end
def handle_info({:chat_message, data}, socket) do
  # This does not fire!
  ...
end

In this code, I’d expect that any listener to topic chat:id would be alerted with the :chat_message data, and the local handle_info would be called. However, when broadcast/3 is called, the local handle_info is not fired, and an error raises from the parent (who is not even subscribed to the topic at all):

function ExampleWeb.ParentView.handle_info/2 is undefined or private

I’m pretty new to Phoenix/Elixir in general, so perhaps I am just using the wrong broadcast or have the wrong mental model around how LC’s function as sub-processes to their LV parent? Maybe this is an actual bug in LV? I’m not sure. For what it’s worth, I am using PubSub features in other pieces of my application and it seems to work as expected, so this seems to be an LV<->LC issue.

Any help would be greatly appreciated. Thank you!

3 Likes

:wave:

have the wrong mental model around how LC’s function as sub-processes to their LV parent?

Live components are not processes hence they can’t have handle_info callbacks since all messages would be received by their “parent” live view anyway.

Any calls to Phoenix.PubSub.subscribe(Example.PubSub, "chat:id") also subscribe the current process (self/0) by default which is the “parent” live view, not the live component.


From the second paragraph in Phoenix.LiveComponent — Phoenix LiveView v0.20.2

Components run inside the LiveView process, but may have their own state and event handling.

2 Likes

Thank you for the remarkably quick response! I am disappointed to hear that my PubSub idea doesn’t work within LC’s, but your explanation makes a lot of sense as to why it was breaking.

At this point, I’m assuming my best path forward is to make a macro which defines the handle_event code and drop it into parents that use the chat component. I think that’d keep me on track for being able to drop-in the functionality while modifying parents as little as possible (but feel free to correct me if I’m wrong!).

Thank you again for your help! I was really scratching my head over this.

2 Likes

Or maybe you can replace live components with live views? Then PubSub would work as expected.


I had a similar (I think) problem with a dashboard consisting of widgets where dashboard was a live view and each widget was a live component.

At first it was working fine but then I decided that I want to fetch the data for each widget asynchronously. I tried calling Task.async in the widget component, then received the response in the dashboard live view’s handle_info and called send_update there as well to update the widget component (they were “stateful” since they also needed to be able to handle some user interactions).

It was a bit complicated, so eventually I just went with making each widget a live view.

1 Like