How to differentiate clicked button in form from others?

I have a LiveView with a form that has 2 buttons, a skip button and a submitbutton.

Both buttons have a CSS that checks if the button has the phx-submit-loading and shows a loading svg.

The issue is that I only want to change the loading in the button that I clicked, so, if I click in the skip button, I want to show the loading SVG in that button until the server replies. Same thing for the submit button.

But right now, it seems that LiveView will add the phx-submit-loading to all buttons instead of the one I clicked, and it doesn’t add any other class to the button I clicked. meaning that I don’t have a way to differentiate between them, and the loading svg ends up showing in both buttons which seems confusing for the user.

I think you’re going to have to use a client side hook or get fancy with JS commands here.

The simplest non-hook approach I can come up with is:

def click do
  |> JS.remove_class("clicked", to: "button") # Will remove `clicked` class from all buttons
  |> JS.add_class("clicked") # Add `clicked` class to clicked button

Then add phx-click={click()} to the appropriate buttons and target in CSS with:

.phx-submit-loading.clicked .icon {
  display: block;

This will leave the clicked class on the button even after loading. Not super clean but also not a big deal if you target properly in CSS (it’s more of a “last clicked” class).

You could also use a client-side hook and put it somewhere where it will apply to all buttons if that is what you’re after.

Yeah, that is a good workaround, thanks for the tip I will use it in my project :slight_smile:

It still sucks. I mean, AFAIK, LiveView knows exactly the button that was clicked, it could, for example, add phx-submit-loading to all buttons inside the form, but also add phx-click-loading to the button that was clicked.

It does already add the phx-click-loading class to a button that is outside a form.

Gah, I gotta reread the docs, I wasn’t aware of phx-click-loading. I guess you could just put some sort of no-op click on the buttons you want this to apply to. You could always request it get added (or something similar to it). I think it’s far more common to have a global loader in conjunction with phx-disable-with so my guess is that it just hasn’t come up yet.

I just opened a issue to see if they can add that, let’s see what the devs say :slight_smile:

1 Like

Unless we’re unaware of it and you already can, it would actually be nice to be able to add a temporary class with some variant of phx-disable-with. Though having phx-click-loading is probably enough.

Oh, for sure, that would be even better, I would even go further and add options to customize all the phx-* classes in some way.

Now that you mentioned phx-disable-with, that confirmed to me that LiveView does know what button was clicked since it will only apply that attribute change to the clicked button, not the others :slight_smile:

Oh, weird, I tested before I answered and thought it applied to all. Though I actually only looked for the disabled attribute and nothing else but ya, of course that makes sense it only applies to the one, haha. Since that’s the case you can actually do what you want now with:

[phx-disable-with] .loader-icon {
  /* ... */

Oh, actually… I talked too soon… I forgot to add the phx-disable-with to my other button :sweat_smile:

It does apply to all of them :smiling_face_with_tear:

Ah ok, I thought so but was tired of spinning up Phoenix apps to check :joy:

1 Like

There is a css only way of doing this :wink: remember that the clicked button will be in focus state.

Doesn’t seem to work for me. Also, won’t the button lose focus if the user clicks anywhere else?

1 Like

Yes. I’m all for CSS-only solutions—the more we get the better! But this isn’t a viable solution here, even if most people will never run into it.

1 Like

Great news, Chris closed my issue in Github with a commit that makes the phx-submit-loading class only be applied to the button you clicked during a form submit :grin:

It is in main for now, but I already tested and it does solve the problem.

1 Like