Skip to content

Streams of Nested LV with hooks throws exception #3530

@oohnoitz

Description

@oohnoitz

Environment

  • Elixir version (elixir -v): 1.17.3
  • Phoenix version (mix deps): 1.7.14
  • Phoenix LiveView version (mix deps): 1.0.0-rc7
  • Operating system: Linux
  • Browsers you attempted to reproduce this bug on (the more the merrier): FireFox, Chrome
  • Does the problem persist after removing "assets/node_modules" and trying again? Yes/no: Yes

Actual behavior

This may be a niche usage since it is considered resource intensive. However, the following error is thrown when using patch modify a stream that renders nested LVs. This has previously worked on v1.0.0-rc5 and seems related to the new createHook support for CustomElement: e86c1b1

Uncaught (in promise) TypeError: hook is undefined
    addHook http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3912
    maybeAddNewHook http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3703
    performPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3718
    trackAfter http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:2027
    trackAfter http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:2027
    perform http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:2273
    perform http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:2273
    performPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3747
    update http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3880
    time http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4858
    update http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3877
    applyPendingUpdates http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3940
    applyPendingUpdates http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:3940
    pushLinkPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4635
    after http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5522
    requestDOMUpdate http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4875
    pushLinkPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4628
    promise callback*pushLinkPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4626
    pushHistoryPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5335
    withPageLoading http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5328
    pushHistoryPatch http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5334
    bindNav http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5299
    after http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5522
    requestDOMUpdate http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4875
    bindNav http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5297
    bindNav http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5284
    bindTopLevelEvents http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:5094
    doConnect http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4803
    connect http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4815
    connect http://localhost:5001/assets/phoenix_live_view/phoenix_live_view.js:4815
    <anonymous> http://localhost:5001/?q=a:12
Application.put_env(:sample, Example.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 5001],
  server: true,
  live_view: [signing_salt: "aaaaaaaa"],
  secret_key_base: String.duplicate("a", 64)
)

Mix.install([
  {:plug_cowboy, "~> 2.5"},
  {:jason, "~> 1.0"},
  {:phoenix, "~> 1.7"},
  # please test your issue using the latest version of LV from GitHub!
  {:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "main", override: true},
])

# build the LiveView JavaScript assets (this needs mix and npm available in your path!)
path = Phoenix.LiveView.__info__(:compile)[:source] |> Path.dirname() |> Path.join("../")
System.cmd("mix", ["deps.get"], cd: path, into: IO.binstream())
System.cmd("npm", ["install"], cd: Path.join(path, "./assets"), into: IO.binstream())
System.cmd("mix", ["assets.build"], cd: path, into: IO.binstream())

defmodule Example.ErrorView do
  def render(template, _), do: Phoenix.Controller.status_message_from_template(template)
end

defmodule Example.NestedLive do
  use Phoenix.LiveView

  def mount(_params, session, socket) do
    {:ok, assign(socket, :item_id, session["item_id"])}
  end
  
  def render(assigns) do
   ~H"""
    <div id={"item-outer-#{@item_id}"}>
      test hook with nested liveview
      <div id={"test-hook-#{@item_id}"} phx-hook="test"></div>
    </div>
    """ 
  end
end

defmodule Example.HomeLive do
  use Phoenix.LiveView, layout: {__MODULE__, :live}

  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign(:count, 3)
      |> stream_configure(:items, dom_id: &"item-#{&1.id}")

    {:ok, socket}
  end

  def handle_params(_params, _uri, socket) do
    socket =
    socket
      |> stream(:items, [%{id: 1}, %{id: 2}, %{id: 3}], reset: true)

    {:noreply, socket}
  end

  def render("live.html", assigns) do
    ~H"""
    <script src="/assets/phoenix/phoenix.js"></script>
    <script src="/assets/phoenix_live_view/phoenix_live_view.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
      let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket, {
        hooks: {
         test: {
           mounted() { console.log("aa") }
         }
        }
      })
      liveSocket.connect()
    </script>
    <style>
      * { font-size: 1.1em; }
    </style>
    <%= @inner_content %>
    """
  end

  def render(assigns) do
    ~H"""
    <ul id="stream-list" phx-update="stream">
      <%= for {dom_id, item} <- @streams.items do %>
        <%= live_render(@socket, Example.NestedLive, id: dom_id, session: %{"item_id" => item.id}) %>
      <% end %>
    </ul>
    <.link patch={"/?q=a"}>patch a</.link>
    <.link patch={"/?q=b"}>patch b</.link>
    <div phx-click="inc">+</div>
    """
  end
  
  def handle_event("inc", _params, socket) do
    socket = 
      socket
      |> update(:count, & &1 + 1)
      |> then(&stream_insert(&1, :items, %{id: &1.assigns.count}))

    {:noreply, socket}
  end
end

defmodule Example.Router do
  use Phoenix.Router
  import Phoenix.LiveView.Router

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

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

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

defmodule Example.Endpoint do
  use Phoenix.Endpoint, otp_app: :sample
  socket("/live", Phoenix.LiveView.Socket)

  plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix"
  plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view"

  plug(Example.Router)
end

{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
Process.sleep(:infinity)

Expected behavior

We should still be able to interact with the page so no exception was thrown during the DOM patching process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions