Liveview: Sending a message to intermediate component?

Hi All.

I have a standard LiveView and Form Component (in fact both were generated by phx.gen.live). In my FormComponent I have a SelectComponent. My SelectComponent receives a message via phx-click and processes it via handle_event. All works fine and dandy.

I now want my handle_event in the SelectComponent to send a message to the FormComponent similar to:
send(self(), {:updated_select, %{field: socket.assigns.field, id: id}})

but the above code sends the messge to the top level LiveView. I’m sure other folks have had a similar challenge but I’ve spent a couple of hours searching and can’t find the answer.

So, how would I send a message to my parent which is a component not the grandparent which is the top level view?

many thanks for any help.

cheers

2 Likes

You have a couple options, but each will result in you calling LiveView.send_update/3, the choice is where you call it :slight_smile:

Option 1: ComponentA → LiveView → ComponentB

You have the first step for this already: from SelectComponent send a message to the LiveView.

Next, in the LiveView callback, trigger an update to FormComponent:

# foo_live.ex
def handle_info({:updated_select, %{field: field, id: id}, socket) do
  FormComponent.updated_select(id, field)
  {:noreply, socket}
end

You need to define the API used to update FormComponent. From within that API, call send_update/3:

# form_component.ex
def updated_select(id, field) do
  send_update(__MODULE__, id: id, updated_select: field)
end

Finally in your FormComponent.update/2 callback you handle receiving the new assigns:

# form_component.ex
def update(%{updated_select: field} = assigns, socket) do
  # this is the default behaviour of the update callback
  socket = assign(socket, assigns)
  # do something with the updated select
  {:ok, socket}
end

The advantage to this approach is that the components (theoretically) stay de-coupled since the LiveView acts as the mediator between them.

Option 2: Update the FormComponent directly

If SelectComponent knows the component id of the FormComponent, then you can trigger its API directly:

# select_component.ex
def handle_event("change", params, socket) do
  FormComponent.updated_select(socket.assigns.form_cid, ...)
  {:noreply, socket}
end
5 Likes

Cheers for this. I like Option 2 - I know it couples the two components together but my SelectComponent is always going to used in the context of a FormComponent so I don’t think this is an issue.

One last question if I may. How do I get the id of the FormComponent (the form_cid in your example)?

cheers

Dave

send_update/3 requires the module name and the stateful component id that you provided to live_component/3.

Somewhere in your template:

# foo_live.html.leex
live_component(@socket, FormComponent, id: this_is_the_form_cid, ...)

From within the FormComponent template, it’s socket.assigns.id, so you can provide it to the live_component call for SelectComponent:

# form_component.html.leex
live_component(@socket, SelectComponent, id: select_component_id, form_cid: @id)

Note also that you can reuse ids across different components, so rendering SelectComponent like so would be perfectly valid:

# form_component.html.leex
live_component(@socket, SelectComponent, id: @id, ...)

… and then you only have one id value to worry about :slight_smile:

# select_component.ex
def handle_event("click", params, socket) do
  FormComponent.updated_select(socket.assigns.id, ...)
  {:noreply, socket}
end
1 Like

Awesome - thank you so much for your help on this.

cheers

Dave

1 Like

Hi @mcrumm

That worked perfectly so many thanks.

Just a question about style - I can actually call send_update (to the FormComponent) from my SelectComponent but you suggested defining a API (updated_select). Is this just good form or are there reaons why you wouldn’t call send_update directly. It looks a bit like a Genserver style API definition so just curious.

many thanks

Dave

You caught me, I was being opinionated :smiley:

send_update/3 is a pretty open-ended API. You are literally passing new assigns directly into your component, albeit with update/2 providing the true boundary. I prefer still to keep the LiveComponent solely responsible for its own assigns, thus the reason for the component API.

1 Like

LOL. I understand - thanks for the explanaition.

1 Like