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

select inputs not being updated in the DOM in response to server updates when unfocused #3083

Open
jearbear opened this issue Feb 3, 2024 · 2 comments · May be fixed by #3127
Open

select inputs not being updated in the DOM in response to server updates when unfocused #3083

jearbear opened this issue Feb 3, 2024 · 2 comments · May be fixed by #3127

Comments

@jearbear
Copy link

jearbear commented Feb 3, 2024

Environment

  • Elixir version (elixir -v): 1.15.4
  • Phoenix version (mix deps): 1.7.0
  • Phoenix LiveView version (mix deps): 0.20.0
  • Operating system: Mac
  • Browsers you attempted to reproduce this bug on (the more the merrier): Chrome, Firefox
  • Does the problem persist after removing "assets/node_modules" and trying again? Yes/no: Yes

Actual behavior

I have a select field that's being updated by the server. When I load the page it will update just fine, reflecting updates that are driven by the server without any user interaction. When I focus and then blur the select input, the server updates stop taking effect on the DOM. I can confirm in the browser console that the server is still sending diffs to update the DOM, but these updates are never happening!

It makes sense to me that the DOM is not updated when the input is focused (as per the docs), but what is surprising is that when the input loses focus, it still doesn't get updated. I can confirm that the select input does not have focus with document.activeElement in the browser console, which returns the root body tag.

I've also noticed that if I focus another input element, updates start taking effect on the select again, but when that text input loses focus, updates stop taking effect on the select. I haven't seen this behavior with regular text inputs. They will resume updates just fine as expected once they lose focus.

Very interested to see if anyone knows what's going on here, thanks!

Expected behavior

Select inputs should resume updating from the server once they lose focus.

Repro

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.0"},
  {:phoenix_live_view, "~> 0.20.0"},
  {:ecto, "~> 3.10"},
  {:phoenix_ecto, "~> 4.4"}
])

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

defmodule Fields do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:ids, {:array, :id})
  end

  def changeset(form, params) do
    form
    |> cast(params, [:ids])
    |> validate_required([:ids])
  end
end

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

  def mount(_params, %{}, socket) do
    if connected?(socket), do: :timer.send_interval(1000, self(), :tick)

    changeset = Fields.changeset(%Fields{}, %{})
    {:ok, socket |> assign(options: [1, 2, 3, 4, 5], form: to_form(changeset))}
  end

  def handle_info(:tick, socket) do
    selected = Enum.take_random([1, 2, 3, 4, 5], 2)

    changeset =
      Fields.changeset(%Fields{}, %{ids: selected})

    {:noreply, socket |> assign(form: to_form(changeset))}
  end

  defp phx_vsn, do: Application.spec(:phoenix, :vsn)
  defp lv_vsn, do: Application.spec(:phoenix_live_view, :vsn)

  def render("live.html", assigns) do
    ~H"""
    <script src={"https://cdn.jsdelivr.net/npm/phoenix@#{phx_vsn()}/priv/static/phoenix.min.js"}></script>
    <script src={"https://cdn.jsdelivr.net/npm/phoenix_live_view@#{lv_vsn()}/priv/static/phoenix_live_view.min.js"}></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 handle_event("validate", params, socket) do
    {:noreply, socket}
  end

  def render(assigns) do
    ~H"""
    <.form id="form" for={@form} phx-change="validate">
      <select
        id={@form[:ids].id}
        name={@form[:ids].name<>"[]"}
        multiple={true}
        >
        <%= Phoenix.HTML.Form.options_for_select(@options, @form[:ids].value) %>
      </select>
      <input type="text" placeholder="focus me!"/>
    </.form>
    """
  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(Example.Router)
end

{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
Process.sleep(:infinity)
@jearbear jearbear changed the title select inputs not being updated in the DOM in response to server updates select inputs not being updated in the DOM in response to server updates when unfocused Feb 3, 2024
@jadengis
Copy link

jadengis commented Feb 3, 2024

@jearbear Is your select nested in a <form> tag? I think there is a bug in dom.js that causes the view update to fail on the frontend for inputs that are not nested in a form. Check to see in the JS console if you get an error from the dom.js function maybeHideFeedback.

I was running into a similar issue after updating with one of my components. Looks like @chrismccord put a patch in for this earlier today though. Commit: a222c7f

@jearbear
Copy link
Author

jearbear commented Feb 3, 2024

Yup, it's in a form tag. I tried this again on main and am getting weirder behavior. Now the select input keeps being given focus on server updates (which also prevents it from having its values updated by the server). The problem goes away if I focus any other input, but comes back if I unfocus that input.

Screen.Recording.2024-02-03.at.8.26.56.AM.mov

Repro

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.0"},
  {:phoenix_live_view,
   git: "https://github.com/phoenixframework/phoenix_live_view.git", branch: "main"},
  {:ecto, "~> 3.10"},
  {:phoenix_ecto, "~> 4.4"}
])

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

defmodule Fields do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:ids, {:array, :id})
  end

  def changeset(form, params) do
    form
    |> cast(params, [:ids])
    |> validate_required([:ids])
  end
end

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

  def mount(_params, %{}, socket) do
    if connected?(socket), do: :timer.send_interval(1000, self(), :tick)

    changeset = Fields.changeset(%Fields{}, %{})
    {:ok, socket |> assign(options: [1, 2, 3, 4, 5], form: to_form(changeset))}
  end

  def handle_info(:tick, socket) do
    selected = Enum.take_random([1, 2, 3, 4, 5], 2)

    changeset =
      Fields.changeset(%Fields{}, %{ids: selected})

    {:noreply, socket |> assign(form: to_form(changeset))}
  end

  defp phx_vsn, do: Application.spec(:phoenix, :vsn)
  defp lv_vsn, do: Application.spec(:phoenix_live_view, :vsn)

  def render("live.html", assigns) do
    ~H"""
    <script src={"https://cdn.jsdelivr.net/npm/phoenix@#{phx_vsn()}/priv/static/phoenix.min.js"}></script>
    <script src={"https://cdn.jsdelivr.net/npm/phoenix_live_view@#{lv_vsn()}/priv/static/phoenix_live_view.min.js"}></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 handle_event("validate", params, socket) do
    {:noreply, socket}
  end

  def render(assigns) do
    ~H"""
    <.form id="form" for={@form} phx-change="validate">
      <select
        id={@form[:ids].id}
        name={@form[:ids].name<>"[]"}
        multiple={true}
        style={"width: 200px; height: 200px"}
        >
        <%= Phoenix.HTML.Form.options_for_select(@options, @form[:ids].value) %>
      </select>
      <input type="text" placeholder="focus me!"/>
    </.form>
    """
  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(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
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants