LiveView send_update not rendering

Hello guys, I’ve been trying to re render specific components based on events. On my parent liveview I’m sending an update via send_update to a component with its ID. It seems that the component is receiving the update, I’m inspecting on the update/2 function of the component, however the component does not finish it’s lifecycle and does not re render on those cases the parent sends an update. Am I missing something ? isn’t the component supposed to re render every time the assigns are updated, (which I am on the component, every time on the update function).

You are not missing anything. Can you extract your scenario into a demo project and put that somewhere?

Hi,

I had a similar problem that I solved by using a single root tag for my component. If you have multiple tags at the root of your component template, try using only one to see if it solves the problem.

For example, in my case, this template does not update after send_update that sets @show to true, value “shown” is never visible:

<div></div>
<%= if @show do %>
    <div>shown</div>
<% end %>

Changing the template to this does work, “shown” is visible after send_update:

<div>
  <div></div>
  <%= if @show do %>
      <div>shown</div>
  <% end %>
<div>

I just reproduced this in my project with LV 0.14.

@Softknobs that’s because components require HTML tags at their root (see docs).

This is the parent LiveView, whenever the PubSub has a new message, it handles it and sends and update to the corresponding component:

defmodule App.BoardLive do
  use Phoenix.LiveView, layout: {App.LayoutView, "live.html"}

  alias App.{BoardView, BoardComponent}

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket), do: Phoenix.PubSub.subscribe(App.PubSub, "update")

    {:ok, socket}
  end

  @impl true
  def render(assigns), do: Phoenix.View.render(BoardView, "board_live.html", assigns)

  @impl true
  def handle_info(%{id: id}, socket) do
    send_update(BoardComponent, id: id)

    {:noreply, socket}
  end
end

And this is the component module, which should re-render when the assigns are modified:

defmodule App.BoardComponent do
  use Phoenix.LiveComponent

  def update(%{id: id}, socket) do
    IO.inspect "updating..."

    {:ok, assign(socket, id: id)}
  end

  def render(assigns) do
    IO.inspect "rendering"

    ~L"""
    <canvas id="<%= @id %>" class="board board-square" phx-hook="Canvas" phx-click="clicked" phx-target="<%= @myself %>" data-pixels="<%= get_pixels(@id) %>" width="30" height="30"></canvas>
    """
  end

When the parent sends the update to the child, only the inspect from the update/2 is fired, it is not executing the rendering function. Besides this issue, the component renders normally as you would expect from a statefull component.

It is not clear to me what you are trying to achieve with such code. Are you trying to change the value of ìd? id has a special meaning for send_update. You can’t change the id with send_update.

When you call send_update(BoardComponent, id: id) you are not updating anything because id is the id of the BoardComponent you are sending an update to. Since you are not giving a value to change, update/2 is called but does nothing because there is nothing to update.

To update your livecomponent you should at least send a new value, for example:
send_update(BoardComponent, id: component_id, board_id: board_id) and handle it with update:

def update(%{board_id: board_id}, socket) do
  IO.inspect "updating..."
  {:ok, assign(socket, board_id: board_id)}
end

# default update when not updating board_id
def update(assigns, socket) do
 {:ok, socket |> assign(assigns)}
end

Then do the logic needed with the board_id in your template.

@dgreiss Thanks, yes I knew this but it is not clear in the docs if the limitation is for the template or the rendered component. In the given examples, the rendered component only has tags at its root.

2 Likes

To add to @Softknobs’s reply, if all you want is to force a full re-render of a stateful live component, one way to achieve that would be to simply update/change it’s id.

1 Like

This is kinda old, but I wanted to just give a shout out for solving some massive frustration for me. I forgot the root tag on a component and was pulling my hair out because send_update didn’t appear to trigger the re-render.

The lack of a root element was the issue! Thanks.

2 Likes