Created
November 20, 2025 14:16
-
-
Save derekkraan/bef48f9049049b308d4c8e27439e8c59 to your computer and use it in GitHub Desktop.
Demonstrating infinite scroll bug in LV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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} | |
| ]) | |
| # if you're trying to test a specific LV commit, it may be necessary to manually build | |
| # the JS assets. To do this, uncomment the following lines: | |
| # 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.BugLive do | |
| use Phoenix.LiveView, layout: {__MODULE__, :live} | |
| alias Phoenix.LiveView.JS | |
| def mount(params, _session, socket) do | |
| {:ok, | |
| socket | |
| |> assign(:add_bottom, params["add_bottom"]) | |
| |> assign(:bottom_count, 0) | |
| |> stream(:items, items(50), reset: true)} | |
| end | |
| defp items(number) do | |
| 0..number | |
| |> Enum.map(fn n -> %{id: "dom-#{n}", label: "#{n}th item"} end) | |
| end | |
| def handle_event("add-bottom", _params, socket) do | |
| {:noreply, socket |> assign(:add_bottom, true)} | |
| end | |
| def handle_event("bottom", _params, socket) do | |
| {:noreply, socket |> assign(:bottom_count, socket.assigns.bottom_count + 1)} | |
| 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> | |
| <%!-- uncomment to use enable tailwind --%> | |
| <%!-- <script src="https://cdn.tailwindcss.com"></script> --%> | |
| <script> | |
| let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket) | |
| liveSocket.connect() | |
| </script> | |
| <style> | |
| * { font-size: 1.1em; } | |
| </style> | |
| {@inner_content} | |
| """ | |
| end | |
| def render(assigns) do | |
| ~H""" | |
| <div style="padding: 8rem;"> | |
| {if @add_bottom, do: "HAS BOTTOM", else: "HAS NO BOTTOM"} | |
| <.link href={"/?add_bottom=1"} class="border border-2 rounded-lg p-2"> | |
| Go to page with bottom | |
| </.link> | |
| or | |
| <.link href={"/"} class="border border-2 rounded-lg p-2">Go to page with no bottom</.link> | |
| and <button phx-click="add-bottom" class="border border-2 rounded-lg p-2">Add Bottom</button> | |
| <div | |
| id="stream_container" | |
| style="height: 50rem; width: 50rem; border: 1px blue solid; padding: 1rem; overflow: scroll;" | |
| class="h-[50rem] w-[50rem] border border-blue overflow-scroll p-4" | |
| phx-update="stream" | |
| phx-viewport-bottom={@add_bottom && JS.push("bottom")} | |
| > | |
| <div :for={{dom_id, item} <- @streams.items} id={dom_id} class="h-8 border"> | |
| {item.label} | |
| </div> | |
| </div> | |
| <div class="">Got to bottom {@bottom_count} times</div> | |
| </div> | |
| """ | |
| 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("/", BugLive, :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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment