I’ve been using Liveview in my daily job and side hustle for over 1 year right now. So far I love it and I find that my productivity increased a lot with it. But there is one specific part of it that I find lacking in good practices and non-complex solutions. That part would be latency handling.
For example. Let’s say I want to open a modal in my liveview, there are two good ways to do that that I can think about. One is changing the route to a route that will show the modal.
For example, I’m in this route /my_page
and if I click in a button to show a modal, I will do a patch navigation to /my_page/modal
that will show the modal.
That works, but it doesn’t handle latency well because it will only show the modal after a round-trip to the server is done. So the user experience can suffer and fell sluggish or non-responsive depending on the user latency (Sure, I can show a loading svg in the button with phx-click-loading
or something like that, but that is still a worse experience than just show the modal right away).
Another way is to use the JS
module and do something like JS.show
to show the model instantaneously entirely from the client side.
This handle latency well, but it fails for crash/server restart scenarios since in these cases, liveview will not know that the modal should be open and will just re-render the page with it closed, meaning that if an user is doing something inside a modal when I decide to deploy a new version, that user will lose all the work because of the restart.
Another example is a select component that I created because I wanted a select that an user can search.
My first version was to create a component that would receive the FormField
see which value is selected and show that as the selected value in the component.
The issue with that approach, again, is latency, if the latency is high, the selected value will take some time to show up as the component selected value.
After that, I used the JS
module to update that from the client side so the selected value would be updated in real-time regardless the latency.
That works, but if the latency is high, when the new selected value event reaches the server, it will update the FormField
, call the component update
function and it will override the selected value.
Because of that, if I change the selected value multiple times really fast and there is a high latency, when these events reach the server, it will replace the selected value in the client too:
A workaround for that is to add a flag to the component that is set to true after the first update
call to ignore the subsequent ones:
def update(_assigns, %{assigns: %{initialized?: true}} = socket), do: {:ok, socket}
def update(assigns, socket) do
...
{:ok, assign(socket, initialized?: true)}
end
Now the select works great even when there is high latency, but it breaks when the server crashes/restart because, for some reason, the component update
function will be called 2 times, the first time the FormField
will have the value
field set as nil
, and the second one with the correct value, meaning that the first time, it will reach my second update
function and set the value to nil
, but the second time, where it has the correct value
field set, it will be ignored because initialized?
is already true
.
These are just some examples of issues I had when trying to create a liveview that as a good user experience to users that have high latency.
I would love to know if others had the same issue and how they solved it. Feel free to give me suggestion specifically for the examples I showed above, but keep in mind that they are just examples, the point of this topic is to discuss the topic of latency with liveviews and what are the best methods to workaround it.