Is my understanding of component composition/communication model correct?

Hi,

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

 <input
  phx-debounce="100"
  phx-focus={show_results()}
  phx-blur={hide_results()}
  name="search"
  type="text"
  autocomplete="off"
  placeholder="Search by patient name or email"
   value={@selected_patient.name}
/>

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 %>
    <li
      phx-click="patient_selected"
      phx-value-selected-patient-id={@patient.id}
      phx-target={@myself}
   >

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={@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, &(&1.id == selected_patient_id))

  send_update(socket.assigns.on_selected.module,
    id: socket.assigns.on_selected.id,
    selected_patient: selected_patient,
    event: :patient_selected
  )

  {:noreply, socket}
end

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})}
  end

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

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

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.