artem
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_classsomehow 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?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #blog-post
- #ai
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 7- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
caleb-bb
Can you inspect the html after you click? I suspect that you’ll see TWO instances of the
hiddenclass on that element while it’s hidden. If only one of them gets removed, that would explain what’s happening.IF that’s the case, then I suggest adding a conditional to your JS event handler to check if the element already has the
hiddenclass before adding it.artem
One instance of “hidden” only. I start suspecting that possibly JS.remove class kind’a removes the DOM leaf that LiveView was supposed to update, so nothing is updated from live view then
I hope I am not the first one who wants to manage UI mostly from LiveView, but with some speculative speedups via
JSlibrary-based manipulations, so hopefully somebody has solved this kind of issue already.D4no0
I have a feeling that the class list is not tracked correctly when liveview wants to patch.
Try and move the entire list for class
microphone-buttonto assigns, in this way when you will change that assign it will patch the entire list.codeanpeace
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/2from the server to tell the client to show the record button again with a client side hook using vanilla JS orJS.exec/liveSocket.execJS+JS.showin a data attribute.artem
Oh, so class added or removed by JS is supposed to stay forever regardless of the server side assigns changes.. not quite expected by me to be honest - I thought that the whole main point of JS module is to speculatively update UI while waiting for the “true” state coming over websocket
If I get it correctly if I ever changed element visibility via JS adding/removing “hidden” class, I have to stick with JS controling their visibility forever. Well, if it is this way, then it is this way. In my particular case, I’ll probably skip using “speculative JS” - too much hassle.
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:
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
if I understand correctly what you want, you don’t need the
JS.add_class/1. you can rely on the addedphx-click-loadingclass. remove the|> JS.add_class(...)line, and adjust your button class to"fixed bottom-3 ... phx-click-loading:hidden".from Elixir documentation on Bindings:
actually, you don’t even need the
microphone_clicked_js/1function. you can just dophx-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/1is necessary only if you want to enhance the push event (adding thephx-click-loadingclass to a different element, for example).