rhcarvalho
Delaying LiveView.JS commands (avoid quick flash of "Trying to reconnect")
Hello! There are moments in the lifecycle of a LiveView app that the client disconnects and briefly reconnects (e.g.: tab woke up from sleep, or app was redeployed causing a quick socket reconnection).
In those cases, there’s a very fast “flash” of the flash message (by default a red-colored flash message) that disappears before it could ever be read.
So I’m looking into ways to avoid this brief appearance of the disconnected state. Instead, I would like to wait for say 2 seconds and if we’re still offline then show the flash message.
This is the standard flash_group implementation in core components in a new app:
def flash_group(assigns) do
~H"""
<div id={@id}>
<.flash kind={:info} title={gettext("Success!")} flash={@flash} />
<.flash kind={:error} title={gettext("Error!")} flash={@flash} />
<.flash
id="client-error"
kind={:error}
title={gettext("We can't find the internet")}
phx-disconnected={show(".phx-client-error #client-error")}
phx-connected={hide("#client-error")}
hidden
>
<%= gettext("Attempting to reconnect") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
<.flash
id="server-error"
kind={:error}
title={gettext("Something went wrong!")}
phx-disconnected={show(".phx-server-error #server-error")}
phx-connected={hide("#server-error")}
hidden
>
<%= gettext("Hang in there while we get back on track") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
</div>
"""
end
The show and hide helpers call JS.show and JS.hide.
Is there a way to delay a call to JS.show and perhaps cancel it if phx-connected fires in the meantime?
Do I need to resort to a Hook for greater client-side control of this behavior?
Marked As Solved
rhcarvalho
I found a simpler solution that seems to work as intended. It consists of simply adding animation-delay to the appropriate element permanently and unrelated to the transition classes used in show/2, no JavaScript involved.
- Update the
CoreComponents.flash/1component to take extra classes:
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
+
+ attr :extra_classes, :string,
+ default: nil,
+ doc: "the extra CSS classes to add to the flash container"
+
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
class={[
"fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
@kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
- @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
+ @kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900",
+ @extra_classes
]}
{@rest}
>
- Update only the
client-errorflash inCoreComponents.flash_group/1:
id="client-error"
kind={:error}
title="We can't find the internet"
+ extra_classes="delay-[3s]"
phx-disconnected={show(".phx-client-error #client-error")}
phx-connected={hide("#client-error")}
hidden
Also Liked
mjrusso
There’s a related issue with using LiveView apps on mobile that these solutions don’t address.
Scenario:
- LiveView-based web app is loaded in mobile browser (or embedded web view in a mobile app)
- User backgrounds the browser or mobile app
- After some time (depends on OS and a number of factors, but say at least a minute or so), user returns to browser or app
Assuming that the OS hasn’t unloaded the web page entirely, what you’ll usually see is a quick flash of “trying to reconnect”. The delay doesn’t help (it actually makes it worse because it also delays the hide); I think what’s happening is that the browser is killing the tab’s networking, which causes the “trying to reconnect” flash to render (while the tab isn’t visible to the user), and when you come back later, it’s already on screen.
I think the Page Visibility API can help here. LiveView already has bindings for blur and focus; it’s reasonable to consider including built-in bindings for Document.visibilityState (and changing the logic to only show the flash if the page is visible, and the socket is disconnected).
FWIW, my solution for now is much less complicated: simply make this particular flash look different than the other flashes (just a loading spinner, with “Connecting…” text), and neutral colours that don’t suggest any sort of error:
# A custom flash notice for communicating that the client has disconnected.
def flash(%{id: "client-error"} = assigns) do
~H"""
<div
id={@id}
role="alert"
class="fixed top-2 right-2 z-50 mt-1 mr-2 w-fit rounded-md bg-slate-300 p-3 text-slate-900 shadow-md"
{@rest}
>
<p class="flex items-center gap-1.5 text-sm leading-6">
<span class="font-semibold">
<.icon name="hero-arrow-path" class="mr-2 h-5 w-5 animate-spin" />
<%= gettext("Connecting...") %>
</span>
</p>
</div>
"""
steffend
I’d try using JS.dispatch Phoenix.LiveView.JS — Phoenix LiveView v1.2.5 to send a custom event that then uses liveSocket.execJS.
This is pseudo code, I didn’t test it, but I hope that it shows the idea:
<.flash
id="client-error"
kind={:error}
title={gettext("We can't find the internet")}
phx-disconnected={JS.dispatch("delayed-exec", detail: %{id: "client-error", target: "data-disconnected"})}
phx-connected={JS.dispatch("clear-delayed-exec", detail: %{id: "client-error"}) |> JS.exec("data-connected")}
data-disconnected={show(".phx-client-error #client-error")}
data-connected={hide("#client-error")}
hidden
>
<%= gettext("Attempting to reconnect") %>
<.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
</.flash>
And in your app.js something like:
const timeouts = {};
window.addEventListener("delayed-exec", (e) => {
clearTimeout(timeouts[e.detail.id]);
timeouts[e.detail.id] = setTimeout(() => {
liveSocket.execJS(e.target, e.target.getAttribute(e.detail.target));
}, e.detail.timeout);
});
window.addEventListener("clear-delayed-exec", (e) => {
clearTimeout(timeouts[e.detail.id]);
});
chrismccord
<.flash delay> with <div class={["...", @delay && "delay-2s"]} with attr :delay, :boolean, default: false gets my vote
Last Post!
rhcarvalho
IIRC the approaches I suggested (and use) all come with small tradeoffs.
I know nowadays JS commands that used to be blocking can be made non-blocking and I didn’t reconsider how that would affect the possible solutions.
There were also ideas on how to make the concept of delays more general.
I think this topic would benefit from a fresh pair of eyes ![]()
Sorry I don’t have anything more concrete to share at the moment.
Popular in Questions
Other popular topics
Latest Phoenix Threads
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security









