phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.14k stars 919 forks source link

execJS does not seem to execute. #2979

Closed Adzz closed 9 months ago

Adzz commented 9 months ago

Environment

Actual behavior

If I trigger a JS event that triggers an liveSocket.execJS call which shows / hides something, that show/hiding does not happen.

I can confirm with logging that the correct events fire and the correct attr (containing the JS functions) are found. But the execution of it does not show/hide the elements as expected.

A minimal reproduction can be found in this commit here, I'm not sure if this is the same issue as this https://github.com/phoenixframework/phoenix_live_view/issues/2918

See attached video:

https://github.com/phoenixframework/phoenix_live_view/assets/8428277/b930685b-6859-4835-b703-22b9beaf0442

Expected behavior

I would expect the elements to show hide as expected.

chrismccord commented 9 months ago

Can you verify that you aren't racing the show/hide? Currently you are dispatching the bug trigger, which shows/hides the buttons, but in the same JS ops you are hide/showing the same buttons. I honestly don't know which one wins here, but I would start there to identify the expected behavior. A quick test would be to target different DOM elements than the same ones to verify if the execJS is working. My guess is things are working as expected, and you are swapping the show/hide immediately with the execJS firing before the show/hide in the JS command pipeline

Adzz commented 9 months ago

Oooh interesting. It does feel a bit race condition-y as it is intermittent sometimes.

I added this commit and can see that the third button appears confirming that the execJS is happening but there is some kind of race.

However I tried this, I would expect it to make a difference and it did not help...

      phx-click={
        JS.hide(to: "#active_button")
        |> JS.show(to: "#disabled_btn", display: "inline-block")
        |> JS.dispatch("trigger_bug")
      }

Putting the dispatch last feels like it would complete before the next triggered JS happens.. is that wrong?

Additionally is there any guidance on how to achieve what I need - a btn triggers an API call (chargify), when it does we swap out the button to a disabled one. We need to swap it back when the API responds which happens in JS land.

chrismccord commented 9 months ago

This should work as you want it to under any scenario where you're doing the work out of band in a new sub task (ie a callback, promise, etc). For example your code works as expected if you wrap the execJS in a requestAnimationFrame. Thanks!

~H"""
<div id="active" class="hidden">Active</div>
<button
  type="button"
  phx-click={JS.hide() |> JS.show(to: "#active") |> JS.dispatch("completed")}
  data-on-complete={JS.show() |> JS.hide(to: "#active")}
>
  Test
</button>
"""
window.addEventListener("completed", e => {
  window.requestAnimationFrame(() => {
    liveSocket.execJS(e.target, e.target.getAttribute("data-on-complete"))
  })
})