Difference between socket.root_pid and socket.parent_pid?

Hi! :wave:

I am sometimes using socket.parent_pid or socket.root_pid to send messages from embedded or child LiveViews to their parent. However I don’t really understand the difference. Is the root_pid the routed LiveView? And then parent would be the next parent (given that there could be several layers of liveviews?)

I can’t really find documentation for it in Phoenix LiveView anywhere..

Does anyone know their difference?

Thanks :+1:

From what it looks like in the code:

https://github.com/phoenixframework/phoenix_live_view/blob/2869e28d16b2b514118e3c9c492ef206a8ca3b62/lib/phoenix_component.ex#L946

  def live_render(%Socket{} = parent, view, opts) do
    Static.nested_render(parent, view, opts)
  end

https://github.com/phoenixframework/phoenix_live_view/blob/2869e28d16b2b514118e3c9c492ef206a8ca3b62/lib/phoenix_live_view/static.ex#L212

  def nested_render(
        %Socket{endpoint: endpoint, transport_pid: transport_pid} = parent,
        view,
        opts
      ) do
    config = load_live!(view, :view)
    container = container(config, opts)
    sticky? = Keyword.get(opts, :sticky, false)

    child_id =
      opts[:id] ||
        raise ArgumentError,
              "an :id is required when rendering child LiveView. " <>
                "The :id must uniquely identify the child."

    socket =
      Utils.configure_socket(
        %Socket{
          id: to_string(child_id),
          view: view,
          endpoint: endpoint,
          root_pid: if(sticky?, do: nil, else: parent.root_pid), ################
          parent_pid: if(sticky?, do: nil, else: self()),    ##################
          router: parent.router
        },

https://github.com/phoenixframework/phoenix_live_view/blob/2869e28d16b2b514118e3c9c492ef206a8ca3b62/lib/phoenix_live_view/channel.ex#L1150

  defp mount(%{"session" => session_token} = params, from, phx_socket) do
    %Phoenix.Socket{endpoint: endpoint, topic: topic} = phx_socket

    case Session.verify_session(endpoint, topic, session_token, params["static"]) do
      {:ok, %Session{} = verified} ->
        %Phoenix.Socket{private: %{connect_info: connect_info}} = phx_socket

        case connect_info do
          %{session: nil} ->

            GenServer.reply(from, {:error, %{reason: "stale"}})
            {:stop, :shutdown, :no_state}

          %{} ->
            with {:ok, %Session{view: view} = new_verified, route, url} <-
                   authorize_session(verified, endpoint, params),
                 {:ok, config} <- load_live_view(view) do
              verified_mount(
                new_verified,
                config,
                route,
                url,
                params,
                from,
                phx_socket,
                connect_info
              )


 defp verified_mount(
         %Session{} = verified,
         config,
         route,
         url,
         params,
         from,
         phx_socket,
         connect_info
       ) do
    %Session{
      id: id,
      view: view,
      root_view: root_view,
      parent_pid: parent,
      root_pid: root_pid,
      session: verified_user_session,
      assign_new: assign_new,
      router: router
    } = verified

    %Phoenix.Socket{
      endpoint: endpoint,
      transport_pid: transport_pid
    } = phx_socket

    Process.put(:"$initial_call", {view, :mount, 3})

    case params do
      %{"caller" => {pid, _}} when is_pid(pid) -> Process.put(:"$callers", [pid])
      _ -> Process.put(:"$callers", [transport_pid])
    end

    # Optional parameter handling
    connect_params = params["params"]

    # Optional verified parts
    flash = verify_flash(endpoint, verified, params["flash"], connect_params)

    # connect_info is either a Plug.Conn during tests or a Phoenix.Socket map
    socket_session = Map.get(connect_info, :session, %{})

    Process.monitor(transport_pid)
    load_csrf_token(endpoint, socket_session)

    socket = %Socket{
      endpoint: endpoint,
      view: view,
      transport_pid: transport_pid,
      parent_pid: parent,
      root_pid: root_pid || self(),
      id: id,
      router: router
    }

So each render_live will assign the itself - the renderer pid as a parent.

for root_pid, it will pass what it itself got from its parent. It seems when the socket is created and mounted, the creating process (the socket main channel?) set it self as the root on the newly created socket.