Anyone tried Nested LiveViews?

Wondering if anyone has already crossed this bridge or can point to an example app that has done this?

This is from the LiveView docs

Referencing parent assigns

When a LiveView is mounted in a disconnected state, the Plug.Conn assigns will be available for reference via assign_new/3, allowing assigns to be shared for the initial HTTP request. On connected mount, the assign_new/3 would be invoked, and the LiveView would use its session to rebuild the originally shared assign. Likewise, nested LiveView children have access to their parent’s assigns on mount using assign_new, which allows assigns to be shared down the nested LiveView tree.

# controller
conn
|> assign(:current_user, user)
|> LiveView.Controller.live_render(MyLive, session: %{user_id: user.id})

# LiveView mount
def mount(%{user_id: user_id}, socket) do
  {:ok, assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}
end

Doesn’t really say a lot about how to do it or what would be best practice, just that it’s possible :slight_smile:

I’m curious, are the nested LiveViews rendered from inside the template using live_render(@conn, MyappWeb.NameofviewLive)?

Will a user have a different socket for each LiveView on the page?

Imagine an app that has a bunch of different “widget” blocks on the page that are all their own LiveViews, you can swap in and out different widgets like a calendar widget, weather widget, chat widget, etc. Is this even possible?

You could also use nested LiveViews for 1 on 1 private chats like on Facebook, where every chat is it’s own LiveView. Is this even a good idea?

Just brainstorming and throwing out ideas here, interested to know if anyone has played around with nesting.

3 Likes

I am building an auction application where the main page is one Live View with two nested Live Views, one for simple chat and the other that displays the timer on bidding. From the parent, I render the Live View with something like

<% =live_render(@socket, AuctionRoomWeb.TimerLive, session: %{room_key: @room_key}

It’s pretty straightforward, with one mildly tricky thing being that if you want an event in the child view to trigger something in the parent or at a sibling you need to set up a pubsub topic that both views subscribe to on mount and send messages back and forth that way.

That pumps the brakes on the “widgets” concept a little bit, since every time you add a widget that needs to communicate with other widgets you have to set up and reason about the messages and handlers back and forth and that can escalate quickly. But for things that don’t really influence the rest of the page (like chat), it works great.

1 Like

I tried nested liveviews. I wanted the parent view to manage the state of something because I wanted the parent to pass the same value to multiple child views. So the child views can use that value, and both of them can call back to the parent to update the value for each other.

Instead of forcing it to look for the parent_pid, I wonder about making that a fallback and allowing it to accept any pid as an argument… so you can nest views as deep as you want.

In the parent live_view, embed a child and pass in initial params:

  def render(assigns) do
    ~L"""
      <%= live_render(@socket, MyAppWeb.ALiveViewLive, session: %{something: @something}) %>
    """

Mount the child with those params:

  def mount(%{something: something}, socket) do
    {:ok, assign(socket, something: something)}
  end

Handle an event in the child view, passing event to parent socket:

def handle_event("ur_event_name", event_value, socket) do
  send(socket.parent_pid, {:ur_event_name, event_value})
  {:noreply, self()}
end

Handle it from the parent:

  def handle_info({:ur_event_name, event_value}, socket) do
    {:ok, something} = get_result(event_value)
    {:noreply, assign(socket, something: something)}
  end
13 Likes