0

I have a question about the Phoenix LiveView Component (v0.15.4).

In the documentation, there is an example code:

def handle_info({:updated_card, card}, socket) do
  send_update CardComponent, id: card.id, board_id: socket.assigns.id
  {:noreply, socket}
end

LiveView components do not have a handle_info/2 callback, so send_update/3 is called to redirect events from the parent LiveView to a specific component.

However, I want to send events directly from the parent LiveView to the specific component, because I want to do something like this

# parent LiveView
def handle_info({:new_card, card_id}, socket) do
  send_event CardStackComponent, id: :card_stack, event: "add_card", params: %{"card_id" => card_id}
  {:noreply, socket}
end

# component
def handle_event("add_card", %{"card_id" => card_id} = _params, socket) do
  card = get_card(card_id)

  socket =
    update(socket, :cards, fn cards ->)
      [card | cards]     
    end)
    
  {:noreply, socket}
end

Of course, there is no such function as Phoenix.LiveView.send_event/2. The above is a fictional code to illustrate my intention.

I know how to send events from a client (browser) to a specific component, either by adding the phx-target="<%= @myself %>" attribute to an HTML element, or by using the pushEventTo method in a JavaScript program.

However, as far as I know, there is no way to send an event directly from a parent LiveView to a specific component.

Is there any workaround or better way?

Tsutomu
  • 4,848
  • 1
  • 46
  • 68
  • 1
    I would recommend to stick to the standard way of passing updates to components, however you could always use PubSub and pass the events there. – Daniel Apr 04 '21 at 09:36

2 Answers2

0

Phoenix Pubsub is the answer.

defmodule QuickPick.Cards.LiveUpdates do
  @topic inspect(__MODULE__)

  @doc "subscribe for all cards"
  def subscribe_live_view do
    Phoenix.PubSub.subscribe(QuickPick.PubSub, topic(), link: true)
  end

  @doc "subscribe for specific card"
  def subscribe_live_view(card_id) do
    Phoenix.PubSub.subscribe(QuickPick.PubSub, topic(card_id), link: true)
  end

  @doc "notify for all cards"
  def notify_live_view(message) do
    Phoenix.PubSub.broadcast(QuickPick.PubSub, topic(), message)
  end

  @doc "notify for specific card"
  def notify_live_view(card_id, message) do
    Phoenix.PubSub.broadcast(QuickPick.PubSub, topic(card_id), message)
  end

  defp topic, do: @topic
  defp topic(card_id), do: topic() <> to_string(card_id)
end

Subscribe and handle on updating

def mount(session, socket) do
  LiveUpdates.subscribe_live_view(card.id)
  ...
end

def handle_info({_requesting_module, [:recommendations, :updated], []}, socket) do
  ...
end

Updating

LiveUpdates.notify_live_view(
  card.id,
  {__MODULE__, [:recommendations, :updated], []}
)
Minh-Khang
  • 414
  • 2
  • 10
  • I guess it won't work, as the components are not running under their own process but under LiveView's pid. So, it's the parent LiveView that will receive all pubsub messages – Chris Jun 09 '22 at 15:30
0

you can also do

# parent LiveView
def handle_info({:new_card, card_id}, socket) do
  send_update CardStackComponent, id: :card_stack, card_id: card_id
  {:noreply, socket}
end

#component
def update(%{card_id: card_id} = _assigns, socket) do
  card = get_card(card_id)

  socket =
    update(socket, :cards, fn cards ->
      [card | cards]     
    end)
  {:ok, socket}
end

you have to have in mind that update/2 is going to be called on each render. More info about this here

Is also worth mention that you may want to maintain the state either in the LiveView or the LiveComponent but not both, as is mentioned here

Euen
  • 121
  • 2
  • 3