Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook rendered on send_update does not receive initial events #2703

Closed
jonatanklosko opened this issue Jun 19, 2023 · 2 comments
Closed

Hook rendered on send_update does not receive initial events #2703

jonatanklosko opened this issue Jun 19, 2023 · 2 comments
Assignees

Comments

@jonatanklosko
Copy link
Contributor

We call send_update and the target component renders a new child live component. That new component does push_event on update and renders a hook. The event is correctly sent over the wire, however handleEvent within the hook is not called.

This happens only in case of send_update. it works fine when the component is a part of the initial render, and also works fine when the update is propagated via LV assigns.

Versions

Phoenix v1.7.3, LV v0.19.2

This is a regression between LV v0.18.18 and v0.19.0

Reproduction

Application.put_env(:sample, PhoenixDemo.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 8080],
  server: true,
  live_view: [signing_salt: "examplesalt"],
  secret_key_base: String.duplicate("a", 64)
)

Mix.install([
  {:plug_cowboy, "~> 2.6"},
  {:jason, "~> 1.4"},
  {:phoenix, "1.7.3"},
  {:phoenix_live_view, "0.19.2"}
])

Application.put_env(:nx, :default_backend, EXLA.Backend)

defmodule PhoenixDemo.Layouts do
  use Phoenix.Component

  def render("live.html", assigns) do
    ~H"""
    <script src="https://cdn.jsdelivr.net/npm/phoenix@1.7.3/priv/static/phoenix.min.js">
    </script>
    <script
      src="https://cdn.jsdelivr.net/npm/phoenix_live_view@0.19.2/priv/static/phoenix_live_view.min.js"
    >
    </script>
    <script>
      const hooks = {
        TextRenderer: {
          mounted() {
            const id = this.el.getAttribute("id");
            this.handleEvent(`content_renderer:${id}:content`, ({ text }) => {
              // (4) Finally, this handler should be called
              //   -> it is called in case of regular render
              //   -> it is not called for the send_update render, BUT it is in the payload
              console.log("Content event received");
              this.el.innerHTML = text;
            });
          }
        },
      };

      const liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket, { hooks });
      liveSocket.connect();
    </script>
    <script src="https://cdn.tailwindcss.com">
    </script>
    <%= @inner_content %>
    """
  end
end

defmodule PhoenixDemo.ErrorView do
  def render(_, _), do: "error"
end

defmodule PhoenixDemo.TextComponent do
  use Phoenix.LiveComponent

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

    # (3) This gets sent as part of the patch
    {:ok,
     push_event(socket, "content_renderer:#{socket.assigns.id}:content", %{
       text: socket.assigns.text
     })}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div id={@id} phx-hook="TextRenderer" phx-update="ignore"></div>
    """
  end
end

defmodule PhoenixDemo.SampleComponent do
  use Phoenix.LiveComponent

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.live_component id="text-component-initial" module={PhoenixDemo.TextComponent} text="Initial" />
      <%!-- (2) When we receive update, this gets rendered --%>
      <.live_component :if={@render} id="text-component-dynamic" module={PhoenixDemo.TextComponent} text="Appears!" />
    </div>
    """
  end
end

defmodule PhoenixDemo.SampleLive do
  use Phoenix.LiveView, layout: {PhoenixDemo.Layouts, :live}

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.live_component id="sample" module={PhoenixDemo.SampleComponent} render={false} />
      <button phx-click="send_update">Send update</button>
    </div>
    """
  end

  @impl true
  def handle_event("send_update", %{}, socket) do
    # (1) Send update to the component
    send_update(PhoenixDemo.SampleComponent, id: "sample", render: true)
    {:noreply, socket}
  end
end

defmodule PhoenixDemo.Router do
  use Phoenix.Router

  import Phoenix.LiveView.Router

  pipeline :browser do
    plug(:accepts, ["html"])
  end

  scope "/", PhoenixDemo do
    pipe_through(:browser)

    live("/", SampleLive, :index)
  end
end

defmodule PhoenixDemo.Endpoint do
  use Phoenix.Endpoint, otp_app: :sample

  socket("/live", Phoenix.LiveView.Socket)
  plug(PhoenixDemo.Router)
end

# Application startup

{:ok, _} = Supervisor.start_link([PhoenixDemo.Endpoint], strategy: :one_for_one)

Process.sleep(:infinity)

@chrismccord
Copy link
Member

@jonatanklosko please try main and I'll cut a new release if things are fixed on your side.

@jonatanklosko
Copy link
Contributor Author

@chrismccord the fix works, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants