Find pid of another LiveView for use in send_update

I am new to Elixir and Phoenix, so I apologize if the question is stupid.

I am developing an ecommerce site, and I have a cart drawer located in a layout, using LiveComponent.

After adding an item to cart from another component, that as I understand it, lives in another process, I want to send an update to this LiveComponent so that it updates without a page refresh or navigation.

I found send_update which seems to do what I want Phoenix.LiveView — Phoenix LiveView v1.0.0-rc.1

To use this, I need to find PID of this other Liveview. Things I tried:

  • Process.info(self()) Does not return assigned name of the process
  • Process.register(self(), :cart_liveview) Fails due to a name already existing
  • Using the id that I pass to live_render from the layout, to render everything. Does not find it.

I am using Process.whereis to get PID from name.

Relevant code:

handle_event snippet on product page

      # Save
      properties_enc = Jason.encode!(values)
      Carts.add_to_cart(cart_id, product_id, properties_enc)
      # PID<0.4190.0>
      pid = Process.whereis(:cart_liveview)
      send_update(CartModal, id: "cart_modal", hello: "from_save")

      {:noreply,
       socket
       |> push_event("js-exec", %{
         to: "#confirm-modal",
         attr: "data-show-drawer"
       })}

Liveview load on layout

<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
      <%= live_render(@socket, PhoenixDemoWeb.Layouts.Components.CartLiveView, id: :cart_liveview) %>
    </div>

Live Component load

defmodule PhoenixDemoWeb.Layouts.Components.CartLiveView do
  use PhoenixDemoWeb, :live_view

  alias PhoenixDemoWeb.PutCartCookie
  alias PhoenixDemo.Carts

  @impl true
  def mount(_params, session, socket) do
    # Load cart
    cart_id = session[PutCartCookie.cart_id_cookie_name()]
    cart = Carts.get_cart_with_products(cart_id)
    socket = assign(socket, cart: cart)

    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <.live_component
      module={PhoenixDemoWeb.Layouts.Components.CartModal}
      id="cart_modal"
      cart={@cart}
    />
    """
  end
end

Any ideas? Does what I’m doing even make sense?

You should be using Phoenix.PubSub to communicate between processes – Phoenix.PubSub — Phoenix.PubSub v2.1.3

The LiveView you want should subscribe to a topic and then any other process can broadcast messages to said topic

Don’t try to find the pid of a process, that’ll only cause pain.

2 Likes

Thank you, that explains why I could not find much related to send_update

I’ll implement PubSub and report back.

So I implemented a PubSub system using Phoenix.Channels. Works really well. I don’t have users implemented right now, but if I did, it would even sync the cart between the same user on different computers, which is extremely cool.

I have doubts about wether to use PubSub directly or Channels, Channels added a bit of a layer, essentially it’s the same thing as far as I can tell, so I used that.

Leaving some snippets here in case it’s helpful to someone. In terms of code it is extremely simple:

  1. Subscribe to the channel in the Cart element, which will be receiving messages. A channel can be specific to a user, multiple users or all users, in my case, every user has it’s own channel determined by the cart_id. If all shared the same channel, you can use @topic "channel_name" and then reference it as your channel name in the component.

Handle each message in handle_info, I chose to pass the complete cart but you can do anything.

  @impl true
  def mount(_params, session, socket) do
    cart_id = session[PutCartCookie.cart_id_cookie_name()]

    # Subscribe to cart channel
    PhoenixDemoWeb.Endpoint.subscribe("cart:" <> Integer.to_string(cart_id))

    # Load cart
    cart = Carts.get_cart_with_products(cart_id)
    socket = assign(socket, cart: cart)

    {:ok, socket}
  end

  @impl true
  def handle_info(msg, socket) do
    {:noreply, assign(socket, cart: msg.payload.cart)}
  end
  1. Send data from somewhere else, in my case, when adding product to the cart in the product page:
      new_cart = Carts.add_to_cart(cart_id, product_id, properties_enc)

      PhoenixDemoWeb.Endpoint.broadcast_from(
        self(),
        "cart:" <> Integer.to_string(cart_id),
        "add_to_cart",
        %{
          cart: new_cart
        }
      )

And that is it.

1 Like