Is my understanding of component composition/communication model correct?


My background in components composition is mostly with DDAU in Ember.js. So it naturally serves me as a jump pad as I wrap my head around Live View and wonder how close my mental model is to the idiomatic LiveView approach.

Let me illustrate my thinking process with an example of breaking a modal component further down into smaller components.

I have a Dashboard LiveView within which there is a UploadModal that hosts PatientSelector component.

PatientSelector is a typical typeahead: the clinician types patient names and sees a list of patients whose names match his query.
The selected patient becomes a value of that typeahead and must be propagated to the parent UploadModal component.

      ───────────────────────────────Chronological flow─────────────────────────────────▶     
     │                            Upload Modal Live Component                            │    
     │                                                                                   │    
            │                                      │                            │             
            │                                      │                            │             
       destination                                 │                            │             
 for`on_selected` event                sends `patient_selected`    updates `selected_patient` 
 and `selected_patient`                 event to `on_selected`                  │             
        property                                target                          │             
            │                                      │                            │             
            │                                      │                            │             
      │                          PatientSelector Live Component                          │    
      │                                                                                  │    
                     User selects patient                                                     
                     │       User        │                                                    

The cornerstone of this component is an input which triggers a patient lookup

  placeholder="Search by patient name or email"

and then there is an autocompletion list in which items are entry points to patient selection:

<ul id="upload-modal-patient-selector-results" class="rounded">
  <%= for patient <- @found_patients do %>

As I understand it - I can’t assign a struct to phx-value-*, so instead, I’m setting selected patient id phx-value-selected-patient-id={} .

Then, since PatientSelector keeps a list of found_patients in its state, I can pull Patient from that list in the event handler and send it to the parent component

def handle_event("patient_selected", %{"selected-patient-id" => selected_patient_id}, socket) do
  selected_patient = Enum.find(socket.assigns.found_patients, &(& == selected_patient_id))

    selected_patient: selected_patient,
    event: :patient_selected

  {:noreply, socket}

Now, because selected_patient is passed to the PatientSelector, when I update it in the parent component, the child component also gets it.

<!-- somewhere in the parent component template --> 
<.live_component module={PatientSelector} id="patient-selector-component" selected_patient={@selected_patient} on_selected={%{module: __MODULE__, id: @id}} current_user={@current_user} />
defmodule UploadModal do
  use ObfuscatedWeb, :live_component
  alias PatientSelector

  def mount(socket) do
    {:ok, assign(socket, :selected_patient, %{name: nil})}

  def update(%{event: :patient_selected, selected_patient: selected_patient}, socket) do
    {:ok, assign(socket, :selected_patient, selected_patient)}

  def update(assigns, socket) do
    {:ok, assign(socket, assigns)}

It all works fine, but I wonder if I’m going against the LV programming model with this or not. The primary motivation behind this wiring is to make the upload modal reusable without exposing its wiring/implementation details to hosting LiveView.

Pinging @chrismccord like he’s not busy enough already :joy:

UPDATE: I’m not sure how it happens but after patient being selected for the first time update in the parent component is not triggered, but I still can see selected patient name in the parent component’s template.