Synchronous communication with a root LiveView

Hi,

I’m still making my way around LiveView and I hope I’m missing something that will change this task to embarrassingly simple :slight_smile:

So, I have a simple one input - on button component which only responsibility is to get an address from a user.

 @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.form
        :let={f}
        for={@changeset}
        id="statement-report-form"
        phx-target={@myself}
        phx-submit="save"
        class="x__form-row"
      >
        <.input
          class="x__input x__input--search grow"
          field={{f, :address}}
          type="text"
          autocomplete="off"
          placeholder="Place any bitcoin address here"
        />

        <.button type="submit" class="x__button x__button--primary" phx-disable-with="Loading...">
          Show transactions
        </.button>
      </.form>
    </div>
    """
  end

then it communicates obtained and validated address to the parent live view

  def handle_event("save", %{"x_pub" => xpub_params}, socket) do
    changeset =
      socket.assigns.xpub
      |> XPub.changeset(xpub_params)
      |> Map.put(:action, :validate)

    if changeset.valid? do
      send(socket.root_pid, {:xpub_address_supplied, fetch_change!(changeset, :address)})
    end

    {:noreply,
     socket
     |> assign(:changeset, changeset)}
  end

This report generation process can take seconds, and naturally, I want to leverage out-of-the-box form blocking functionality instead of rolling my own. But send is asynchronous, and the component goes about its business as soon as the message is sent.

As an option, I pass a callback to the child component within which the report is generated. But it feels way too hacky.

if changeset.valid? do
    socket.assigns.on_address_supplied.(fetch_change!(changeset, :address))
end

Root LiveView:

  defp on_address_supplied(xpub) do
    case Scanner.xpub_report(xpub) do
      {:error, :server_error} ->
        send(self(), {:on_report_fetched, :failure})

      {:ok, report} ->
        send(self(), {:on_report_fetched, :success, report["transactions"]})
    end
  end

A couple of thoughts:

  1. Is this form rendered by multiple root views? If it’s not, I’m not sure you’re benefiting from it being a LiveComponent. It seems like the behavior you want to achieve would be simpler if the form were just a function component and you had a single LiveView that you were dealing with.

  2. If you definitely want it to be a LiveComponent, is there a particular reason it can’t just know about Scanner and call into it itself? It seems like encapsulating the call in the root component is creating indirection that’s making things more difficult.