Did I break phoenix live view?

Hello,

Can someone explain me why this simple code doesn’t work as expected ? :slight_smile:

  scope "/", TestWeb do
    pipe_through :browser

    live_session :default do
      live "/", PageLive, :index
      live "/:slug", PageLive, :show
    end
  end
defmodule TestWeb.PageLive do
  use TestWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket |> assign(:slug, "")}
  end

  @impl true
  def handle_params(params, url, socket) do
    {:noreply, socket |> assign(:slug, params["slug"])}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <%= live_render(@socket, TestWeb.BLive, id: "b", session: %{"value" => @slug}) %>
    <%= live_render(@socket, TestWeb.ALive, id: "a", session: %{"value" => @slug}) %>
    """
  end
end
defmodule TestWeb.ALive do
  use TestWeb, :live_view

  @impl true
  def mount(_params, session, socket) do
    {:ok, socket |> assign(:value, session["slug"])}
  end

  @impl true
  def handle_event("click", %{"slug" => value}, socket) do
    {:noreply,
      socket |> assign(:value, value)
    }
  end

  @impl true
  def handle_event("click", _, socket) do
    {:noreply,
      socket
      |> assign(:value, "on")
    }
  end

  @impl true
  def render(assigns) do
    IO.inspect(assigns, label: "A render")
    ~H"""
    A (<%= @value %>) (<%= inspect(@value) %>)
    """
  end
end
defmodule TestWeb.BLive do
  use TestWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
    	socket
    	|> assign(:value, "on")
    }
  end

  @impl true
  def handle_event("click", %{"slug" => value}, socket) do
    {:noreply,
      socket
      |> assign(:value, value)
    }
  end

  @impl true
  def handle_event("click", _, socket) do
    {:noreply,
      socket
      |> assign(:value, "on")
    }
  end

  @impl true
  def render(assigns) do
    ~H"""
    B
	<%= if @value == "off" do %>
		<a data-phx-link="patch" data-phx-link-state="push" phx-click="click" phx-target="#a,#b" href="/on">on</a>
	<% else %>
		<a data-phx-link="patch" data-phx-link-state="push" phx-click="click" phx-value-slug="off" phx-target="#a,#b" href="/off">off</a>
	<% end %>
    """
  end
end

This code is a simple on/off switch, it works in the first few clicks, but then just freeze. I made a short video about this issue https://www.dropbox.com/s/9nuk5r8wdsbzwr8/elixir.mp4?dl=0
My first thought was that this is somehow related to safari browser, because on safari browser it breaks after a few clicks, on firefox or chrome it works a lot better, but when you click long enough it also breaks.

1 Like

Are you able to host your git repo somewhere or post a zip, I will have a poke at it too.

1 Like

Sure, https://www.dropbox.com/s/3x4uhf3fsfzfrw4/test.zip?dl=0

This may not be terribly helpful, but I’m not seeing this issue in either Firefox or Chrome on Linux. I have a distant memory (long before I had heard of Elixir) of there having once been some kind of timeout issue with websockets on Safari, but given how many Elixir/Phoenix developers use MacOS, that surely must have been long fixed.

Also unable to reproduce on Firefox (Linux, macOS) but can reproduce on macOS Safari.

It’s actually pretty consistent there, sometimes happens after the first click.
It may be worth opening a bug to ruin someones tuesday since the behaviour is different between browsers.

What you’re doing is a little bit weird, constructing the patch link yourself, but I get that you’re doing it to update the URL at the same time as firing an internal event.

If you replace your BLive#render with the following, the issue goes away, though obviously you lose the URL changing.

~H"""
    B
    <%= if @value == "off" do %>
      <span phx-click="click" phx-value-slug="on" phx-target="#a,#b">click-on</span>
    <% else %>
      <span phx-click="click" phx-value-slug="off" phx-target="#a,#b">click-off</span>
    <% end %>
"""

To move ahead with your own projects, you might want to have the “on/off” button be a regular live_patch link and catch the URL change in PageLive#handle_params then filter the updated slug down to the child views. This is may or may not suit your needs though. You might also use push_patch in your clickevent handler.

E: I did just see it happen in FF x macOS, now it happens pretty consistently which is odd since previously I had been hitting the button tens (hundreds?) of times and also tried setInterval'ing it without it ever occurring.

3 Likes

It seem that the messages sometimes get out of order and the DOM doesn’t get patched correctly. Even in Firefox (but it takes some more tries to reproduce):

The A component has the wrong state, because the update event for A comes before the update event for the enclosing div (The correct value for A should be on).

On Safari it is the same behaviour, but easier to reproduce:

The last B update doesn’t get applied, because it comes before the phx-… update.

Seems relevant: Track all views with pending updates by rktjmp · Pull Request #1871 · phoenixframework/phoenix_live_view · GitHub

3 Likes

Using my branch does seem to fix it for me after a few minutes of mashing the button.

1 Like

Your code with push_patch works a lot better than my previous code, but slug in the URL doesn’t get updated every time, nonetheless on websocket connection I’m getting a correct value for slug. I think this issue only exists on Safari, I wasn’t be able to reproduce it on Firefox and Chrome.

I’ll check that :slight_smile:

Yes, it’s a weird issue :wink:

This is likely fixed now in 0.17.9.

1 Like