Shortcomings in LiveView - are there any I should look out for?

Yeah, I’d kill for a good way to get JSON data into a hook. Embedding it in the DOM is super error prone, you gotta escape stuff and then unescape stuff, and it dies in a fire if you have a large number of data points.

11 Likes

This would answer a lot of issues but just copying/paste gives me an error: lib/liveapp_web/templates/layout/live.html.leex:52: undefined function connected?/1
The function should be available be default on the second mount inside live.html.leex, right?
Maybe the problem is the first render but what do I need to do? As you have it working I didn’t even though about that…

And to get things even more fuzzy when in one of my live page’s template I have a simple script <script>alert("lol")<script>
of course it works, but when I wrap it inside

<%= if connected?(@socket) do %>
   <script>alert("lol")</script>
<%end%>

it stops working although it’s in the page and I can see it when inspect…
Again, the first and second rendering confusing me…Fundamentals of passing data from the Plug connection to the LiveView

live.html.leex belongs to the LayoutView. So this module needs to import the LiveView functions or you have to do if Phoenix.LiveView.connected?(@socket) do.

3 Likes

Here is my summary of the impact of latency on LiveView. I live in South Africa and our servers is in Europe, I have a 150-200ms ping to them. Our entire app is built in LiveView.

Initial page load: The first response is received in 200ms, the page is not actually “live” yet for another moment while the websocket connects, but users won’t really notice it because they don’t start interacting with the page that quickly. If all links are live_redirect and live_patch then initial page load really only happens the very first time you open the page. SPAs will also have a delay the very first time. This is a non issue.

live_patch and other “same view interactions”: If you click a link or interact and the result is shown within 200ms it feels fast. I would rate live_patch links as giving a good experience even with 200ms latency. Other interactions on the same LiveView (phx_change, etc.) give the same good experience. Again LiveView is the same as an SPA if you ask me.

live_redirect: This is where the problem comes in. You can only use live_patch within the same LiveView. The live_redirect links unfortunately seem to take about 2x or 3x latency. It first sends a phx_leave that takes a full latency trip, then sends a phx_join to open the new page. With a 200ms ping this feels slow and is slower than normal links, but if you revert to normal links then you have the initial page load every time. I think if live_redirect can be adjusted to take one round trip (perhaps a phx_leave_and_join event?), then latency really isn’t an issue for LiveView. The way it is now, it is unfortunately an issue without a good alternative. I’ve logged an issue here:

phx_leave doubles latency on live_redirect links · Issue #1334 · phoenixframework/phoenix_live_view · GitHub

11 Likes

I have posted this on the issue, it is best if we continue the discussion there, but for completeness here is my reply:


To clarify, you should see this:

  1. We do an AJAX request which returns the new LiveView session in its response
  2. Once we get the session on the client, we send a command on the websocket to start the new LiveView

So it is 1 AJAX req/resp and a websocket req/resp - so 2 round-trips.

Chris and I have discussed an alternative solution to this problem - which takes some development to implement but seems to be doable - which is to use message passing to trigger the redirect within the node/cluster. We will aim for something like this:

  1. We do an AJAX request, which will contact the current LiveView and ask it to start a new LiveView with the session (this is a fire-and-forget). We will still return the session.
  2. Once we get the session on the client:
    • If we were already redirected, because of the server message, to the new LiveView, there is nothing to do
    • Otherwise we send a command on the websocket to start the new LiveView (the transport will be the source of truth in case it is racing as the new LiveView may actually already be in flight)

In the best case scenario, this should cut the latency in half (so one regular req/resp) and reduce the amount of requests in half compared to what we do today. The complexity is implementing a atomic replace channel operation on the transport but that should be doable.

11 Likes

One thing i’ve had a little issue with is that say i’ve got a page containing faceted navigation (that is like a online shop where you have filters on the left), and i add the phx-click-loading class to the buttons, i have no pure css way to also change the look of other parts of the page.

So say I select “Clothing Type: Tshirt” in the faceted nav I can make the cursor go to a pointer while it’s loading but i can’t make the list of products grey out while the result of the button click is loading.

From my experiments it seems like phx-page-loading doesn’t get added but what would be great is another class at the top level like phx-page-element-click-loading (with a better name than that!) while any click is loading.

Apologies if there is a way to do this that I’ve missed.

Just a correction on my previous post: after further testing I can confirm it’s 2x latency not 3x. Even with 2x latency the user experience seems as fast as a normal http request, so it’s not worth performing full requests instead of live_redirect to try and improve performance.

I also don’t show NProgress on first page load. The browser will already show a tab loading indicator so the extra NProgress doesn’t seem necessary and is perhaps a bit overzealous in terms of how many things show loading progress.

Secondly if you run a global app it’s a good idea to choose a hosting location that gives good pings to most of your users. You can’t just host in the US and expect everyone to have a good experience regardless of your web framework. Also if you pass all traffic through something like AWS Cloudfront or Global Accelerator it will make the browser connect locally (perform the SSL handshake with low latency, etc.) then use an already active persistent connection within the AWS network to your server. Not to dismiss previous comments in this thread, but if your websockets are taking 1s+ to connect you have an opportunity to improve.

2 Likes

One way to do it:

window.addEventListener("phx:page-loading-start", (info) => {
  if (info.detail.kind === "initial") {
    // Add your loading CSS class to html body or wherever.
  }
})

window.addEventListener("phx:page-loading-stop", (info) => {
   if (info.detail.kind === "initial") {
       // Remove CSS class.
    }
})

Sounds brilliant. I was thinking of making a PR but I don’t think this is a newbie level PR.

Sorry I don’t think I was that clear. I can get the page load events, but i can’t get a top level css class on any button click in progress. It gets added to the element I click but not the top level as well.

How does this not solve your requirement?

window.addEventListener("phx:page-loading-start", (info) => {
  if (info.detail.kind === "initial") {
    window.document.body.className = "some-top-level-loading-class"
  }
})

Okay so i modified it slightly in case I didn’t understand it.

window.addEventListener("phx:page-loading-start", (info) => {
  if (info.detail.kind === "initial") {
    window.document.body.className = "some-top-level-loading-class"
    console.log("adding class")
  }
})

I added an element

<button phx-click="somebutton">hi there</button>

and a handler

  def handle_event("somebutton", %{"value" => ""}, socket) do
    :timer.sleep(10000)
    {:noreploy, socket}
  end

I click the button, the button gets the class “phx-click-loading” and the console doesn’t log anything. I don’t know what your code is trying to do but it doesn’t get executed when I click the button.

What I’ve done in a similar situation is have the <form> tag contain my collection items and then simply use css cascading rules to display the loading placeholder on .phx-change-loading class that LiveView adds to the form’s class on each change.

To achieve the same effect for the initial render of the collection you can use .phx-disconnected to display the loading placeholder. LiveView will replace it with .phx-connected once connected.

CSS pseudo-code below :laughing:

/* default state */
.placeholder-item {
   display: hidden;
}
/* show the loading placeholder */
.phx-disconnected .placeholder-item,
.phx-change-loading .placeholder-item {
   display: block;
}

“initial” is perhaps the wrong event. Try “element” or remove that IF on info.detail.kind just to catch all.

https://hexdocs.pm/phoenix_live_view/js-interop.html#loading-state-and-errors

1 Like

I put a breakpoint at the start of the callback and it only triggers on page load, not when a button is clicked. I believe it would also fire if the button clicked would trigger a page navigation event - maybe that’s what you are saying?

Thanks for your help.

I will read through the code later and see if i can work out an alternative event as well. My main reason for posting here isn’t to ask for some code to add my own handlers, I just wanted to see if other people had this problem and if there is a common pattern which should be baked into LiveView.