artem

artem

How do you control LiveView elements visibility from BOTH server-side and frontend

Hi all

I am still learning Elixir, Phoenix and LiveView. I love the way I can describe the state in my LiveView and it is automagically rendered in frontend. However, right now I am playing with the features where latency happens to be quite important in a couple of places.

I am implementing a tiny sound recorder and I’d really like to hide the “start recording” microphone button ASAP, before the LiveView on server side reacts - so that user wouldn’t click it second time, also fast UI responsiveness looks nice.

Well, that’s where JS module comes into play and I can easily and quickly hide the button with this:

<button
      id="microphone-button"
      class={[
        "fixed bottom-3 ...",
        microphone_button_should_be_visible?(@recording_state) || "hidden"
        ]}
      phx-click={microphone_clicked_js()}
    >
 
 def microphone_button_should_be_visible?(recording_state) do
    recording_state == nil || recording_state == :ready
  end

 def microphone_clicked_js(js \\ %JS{}) do
    js
    |> JS.push("microphone_clicked")
    |> JS.add_class("hidden", to: "#microphone-button")
...
end

As you see I have microphone_button_should_be_visible? function for server side to compute when button should be visible, but since I am pretty sure it is to be hidden on click, I speculatively add hidden class for it not waiting for server side to react.

Unhiding problem
Problem happens when the recording is done and I want to show the microphone button again, so user could initiate the next recording session. My live view correctly sets recording_state assign (I verified it) and microphone_button_should_be_visible? should work correctly, but button never appears unless I use JS to remove “hidden” class.

Could somebody, please, help me understand what’s happening here?

  • Is LiveView-via-heex-based adding-removing “hidden” class not working because JS.add_class somehow added a “distinctively different” attribute that is not controlled by LiveView state anymore?

  • Or does LiveView somehow fail to compute DOM diff when some parts of it are manipulated by JS and I need to somehow explicitly ask it to rerender a particular control?

  • Or am I missing something even more basic? How would you approach the problem?

Most Liked

codeanpeace

codeanpeace

… JS commands are DOM-patch aware, so operations applied by the JS APIs will stick to elements across patches from the server.
source: Phoenix.LiveView.JS

Yup, it’s a feature that can seem like a bug in certain use cases since the typical scenario would have the hide/show and corresponding show/hide be from client driven interactions.

What you could do is push an event via push_event/2 from the server to tell the client to show the record button again with a client side hook using vanilla JS or JS.exec/liveSocket.execJS + JS.show in a data attribute.

# HEEX
<button ... phx-hook="MicButton" data-show={JS.show()}>
  Record
</button>

# LiveView/LiveComponent
push_event(socket, "show-mic-button", %{})

# app.js
let Hooks = {}
Hooks.MicButton = {
  mounted(){
    this.handleEvent("show-mic-button", () =>
      this.liveSocket.execJS(this.el, this.el.getAttribute("data-show"))
    )
  }
}

let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, ...})
D4no0

D4no0

I think this is working as expected, to visualize this in a more clear way, let’s go back to how we would do this in the old days:

class= "fixed bottom-3 <%= microphone_button_should_be_visible?(@recording_state) || "hidden" %>",

It is clear that the only thing the assign knows is how to generate some text in a place, it has no actual concept of dom interaction with class tag like JS. The liveview also can’t track any modifications that are made by JS, so you create an invariant that the server has no idea about.

As I proposed above, try to keep the entire class list in the assign, so it actually rewrites the broken state created by JS with a valid server-side state. I cannot confirm this works 100%, because the patching mechanism is internal and might have changed. It would take a lot of time for me to recreate your setup, so it would be nice if you tried that and came back with a response.

endoooo

endoooo

if I understand correctly what you want, you don’t need the JS.add_class/1. you can rely on the added phx-click-loading class. remove the |> JS.add_class(...) line, and adjust your button class to "fixed bottom-3 ... phx-click-loading:hidden".

from Elixir documentation on Bindings:

All phx- event bindings apply their own css classes when pushed

actually, you don’t even need the microphone_clicked_js/1 function. you can just do phx-click="microphone_clicked", which will immediately add the "phx-click-loading" class in the clicked element, and remove it when your event handler finishes its job (and we can use the "phx-click-loading:hidden" syntax thanks to the custom Phoenix Tailwind variants).

JS.push/1 is necessary only if you want to enhance the push event (adding the phx-click-loading class to a different element, for example).

Where Next?

Popular in Questions Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "eq...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41454 115
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30840 112
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement