What's the proper way to send events from nested live components?

I have a form_component LiveComponent. Inside it is another LiveComponent. How can I send events from the child to the parent? Specifically I want to update the changeset in the form_component.

My FormComponent:

    <.input field={{f, :description}} type="textarea" label="Description" />
    <.live_component
      module={IdoWeb.PhotoInput}
      id="photo-input"
      field={{f, :banner_photo}}
      label="Banner photo"
      changeset={@changeset}
    />

My PhotoInput:

  def handle_event("remove_photo", _, socket) do
    %{field: {_, field}, changeset: changeset} = socket.assigns

    changeset =
      changeset
      |> Ecto.Changeset.put_change(field, nil)

    send(self(), {:update_changeset, changeset})
    {:noreply, socket}
  end

This triggers a warning that I don’t have a handle_info in my live view. I know handle_info doesn’t exist for components, so how am I supposed to send a message to the parent?

Thanks!

You could use send_update

Ah nice, that’s exactly what I needed. This still feels pretty messy, but it works:

FormComponent:

<.live_component
  module={IdoWeb.PhotoInput}
  id="photo-input"
  field={{f, :banner_photo}}
  label="Banner photo"
  changeset={@changeset}
  parent_id={@id}
  parent_module={__MODULE__}
/>

PhotoInput:

def handle_event("remove_photo", _, socket) do
  %{field: {_, field}, changeset: changeset, parent_id: parent_id, parent_module: parent_module} =
    socket.assigns

  changeset =
    changeset
    |> Ecto.Changeset.put_change(field, nil)

  send_update(parent_module, id: parent_id, changeset: changeset)
  {:noreply, socket}
end

Thanks!

Aah, and I spoke too soon. This works sometimes, but not always. I haven’t figured out why, but sometimes send_update will hose all the assigns of the parent component, while other times it seems to properly merge the change in with the parent.

My problem was I didn’t have a specific update function for a changeset. Adding this to the parent fixed it:

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

I’m not sure why it worked half the time before I added this.