Update a Live Component from a Live View using send_update

Hi all,
I would like to send an update to a Live component from a live view.

Here is the snippet from the live view that should send the updates:

@impl true
def handle_info({:job_started, _job}, socket) do
  Logger.error("== JOB STARTED ==")
  send_update(Module.Components.Live.Jobs, id: "jobs", foo: :bar)
  {:noreply, socket}
end

def handle_info({:job_done, _job}, socket) do
  Logger.error("== JOB DONE ==")
  send_update(Module.Components.Live.Jobs, id: "jobs", foo: :baz)
  {:noreply, socket}

In the Module.Components.Live.Jobs component I have something like this:

@impl true
def update(assigns, socket) do
  require Logger
  Logger.error("== JOBS COMPONENT UPDATE ==")
  {:ok, socket}
end

What I get on the console is:

[error] == JOB STARTED ==
[error] == JOBS COMPONENT UPDATE ==
[error] == JOB DONE ==

I don’t get the jobs component update message after the second send_update.

Am I doing something wrong?
What can I do to debug this?

Cheers

Hi @carloratm ,

Sometime I’m facing the same issue using send_update() + update() to update specific component. To be honest, I couldn’t figure out why exactly…

To avoid this, what I do is:

  • i usually don’t use update() in this case with my components but instead I initiate state(s) in my mount() function
    For example:
  @impl true
  def mount(socket) do
    socket =
      assign(socket,
         border_special: ""
      )

    {:ok, socket}
  end

and then by doing that, I can use border_special (for example) wherever I want in my component.

  • and then to update it, in my LV, I write something like that :
  send_update(NameOfComponent,
    id: some_id,
    border_special: border_special
  )

and with that, everything updates perfectly.
Maybe it’s not perfect LV grammar but it works like a charm.
Hope it helps,

Regards

Hi,

Thing is in my case border_special is not computed, it’s coming from the parent view as an attribute.
Which, IIUC, means I have to use the update function.

Hi @carloratm ,
ok, that’s slightly different indeed
In that case (and with no extra context of your app), i usually do pattern matching on update().
For example :

  @impl true
  def mount(socket) do
    {:ok, socket}
  end

  @impl true
  def update(%{border_special: border_special} = assigns, socket) do
    socket =
      socket
      |> assign(assigns)
      |> assign(border_special: border_special)

    {:ok, socket}
  end

  @impl true
  def update(assigns, socket) do
    socket =
      socket
      |> assign(assigns)

    {:ok, socket}
  end

With that, update() works each time and in each case i need it.

1 Like

Thank you, that is exactly what I was doing, but the update is never called the second time.

I’ll do a bit more tests and report back (with more code).

Here is some more code.

This is the live component:

defmodule OHA.Components.Live.Jobs do
  @moduledoc """
  A live component to render long running jobs.
  """

  use OHAWeb, :live_component
  import OHA.Gettext

  @confirm_message dgettext("oha", "Are you sure?")

  @impl true
  def mount(socket) do
    {:ok, socket}
  end

  @impl true
  def update(%{selected_ids: selected_ids} = assigns, socket) do
    require Logger
    Logger.error("== LOG UPDATE HERE ==")

    {:ok,
     socket
     |> assign(assigns)
     |> assign(jobs_started: OHA.LFT.list_started_jobs())
     |> assign(selected_ids_attr: Enum.join(selected_ids, "|"))
     |> assign(jobs_disabled: selected_ids == [])}
  end

  def update(_assigns, socket) do
    require Logger
    Logger.error("== LOG UPDATE THERE ==")
    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="Card mb-4 text-right">
      <div class="w-full text-left">
        <div :for={job <- @jobs_started} class="bg-info text-white p-4 rounded mb-4">
          <span><%= job.id %> - <%= job.payload["run"] %></span>
        </div>
      </div>
      <button
        :for={job <- @jobs}
        type="button"
        phx-click={job.name || "job"}
        phx-value-ids={@selected_ids_attr}
        class={["Btn ml-2", job.class || "", @jobs_disabled && "cursor-not-allowed"]}
        disabled={@jobs_disabled}
        {[data: dataset_of(job)]}
      >
        <%= job.label %>
        <OHA.Components.Icon.icon :if={job.icon} name={job.icon} class="ml-2" />
      </button>
    </div>
    """
  end

  defp dataset_of(%{must_confirm: true}), do: [confirm: @confirm_message]
  defp dataset_of(_), do: []
end

Then I have a live view where I would like to update this component with new assigns (jobs_started):

defmodule OHAWeb.Live.Header do
  @moduledoc false

  use OHAWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket) do
      Phoenix.PubSub.subscribe(OHA.PubSub, "lft:jobs")
    end

    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <p>HEADER</p>
    """
  end

  @impl true
  def handle_info({:job_started, _job}, socket) do
    require Logger
    Logger.error("== JOB STARTED ==")
    jobs_started = OHA.LFT.list_started_jobs()
    send_update(OHA.Components.Live.Jobs, id: "jobs", jobs_started: jobs_started)
    {:noreply, socket}
  end

  def handle_info({:job_done, _job}, socket) do
    require Logger
    Logger.error("== JOB DONE ==")
    jobs_started = OHA.LFT.list_started_jobs()
    send_update(OHA.Components.Live.Jobs, id: "jobs", jobs_started: jobs_started)
    {:noreply, socket}
  end
end

What I get in the console is:

[error] == LOG UPDATE HERE ==
[error] == LOG UPDATE HERE ==
[error] == JOB STARTED ==
[error] == LOG UPDATE HERE ==
[error] == JOB DONE ==

Which in my understanding means the send_update works only once?

Cheers!

Hard to say with what I see…
Points you should explore :

  • check what’s your pubsub subscription is exactly doing, maybe it triggers the first two (?) update()
  • log specific states / or specific messages instead of "== LOG UPDATE HERE ==" so you’ll see what’s going on and maybe which update() function is triggered (if indeed it’s the issue)

Things to consider :

  • sometimes liveview lifecycle leads to unexpected behavior (interest of specific log) (maybe everything is ok but not rendered in the right order in your logs)
  • it’s also curious that your send_update() functions send jobs_started: jobs_started and your update() is expecting %{selected_ids: selected_ids} = assigns
    All that said, I’m far from an expert but maybe debugging like that will help!

Best!

I was indeed the other update pattern to kick in, with the == LOG UPDATE THERE ==

I’ll try more debugging

Thank you