Back to articles

Loading States With Livewire

January 7th, 2024

There are a bunch of ways to show loading states in Livewire. Let's take a look at a few methods, as well as some customisation options to fine tune your UI.

Let's start with the basics and then dive into some practical examples.

The basics

The key element to loading states in Livewire is wire:loading. Used in its most basic form, this is what it might look like.

<div wire:loading>Loading...</div>

If an element with wire:loading is present in your component, that element will appear when an XHR request is being sent to the server to update a Livewire component (basically, when the component is making a request to update).

However, using wire:loading on its own without any customisation will result in Loading... being displayed so quickly it's almost pointless. Ideally, you'll want to add a delay to only show the loading indicator after a specific period has passed.

<div wire:loading.delay.long>Loading...</div>

The above will wait 300ms before showing the loading div, so only slower connections or operations will display the loading text.

An example in practice

Ok, so we've established the basics of how wire:loading works. Let's add it to an example login form so we get the bigger picture.

Here's the Livewire view:

<form wire:submit="submit">
    <div wire:loading.delay.long>Logging you in...</div>

    <div>
        <label for="email">Email address</label>
        <input type="email" name="email" id="email">
    </div>

    <div>
        <label for="password">Password</label>
        <input type="password" password="password" id="password">
    </div>

    <button>Log in</button>
</form>

And here's the submit method within the Livewire component class:

class Login extends Component
{
    public function submit()
    {
        sleep(2);
    }

    //...
}

I've added an artificial delay here so we simulate a slower operation, and we should see the loading indicator display for 1.7 seconds (the two-second sleep minus the 300ms delay).

Customisation

It's pretty unusual to show a loading message nowadays, unless you have specific text you absolutely need to output for context. So, let's look at a few options to make this feel more polished.

Toggling classes

One of the great things about Livewire is it already adds a disabled attribute to submit buttons within forms, to avoid the risk of double clicks while in a loading state. However, we might want to target the submit button so it appears in a loading state.

Let's do that with some opacity. Here's the new Livewire view:

<form wire:submit="submit">
    <!-- Form stuff here -->

    <button wire:loading.class="opacity-50">Log in</button>
</form>

Now (and assuming you're using Tailwind), the opacity-50 class gets added to the button while the component is in a loading state! I've also removed the delay here, since in the case of a form, we'd almost always want the button to show this disabled state immediately.

Swapping button labels

Even for more complex scenarios, where you might want to show a loading spinner or alter the text of a button/element when something is loading, Livewire's loading functionality can handle that.

Let's change the text of the button as well as its opacity.

<form wire:submit="submit">
    <!-- Form stuff here -->

    <button wire:loading.class="opacity-50">
        <span wire:loading.remove>Log in</span>
        <span wire:loading>Logging you in...</span>
    </button>
</form>

Hmm, what's happening here? Well, we're now introduced to the .remove modifier, which will remove an element when a component is loading. So in this example, Log in will be removed when loading and Logging you in will be shown when loading. Basically, we swap the span that's displayed.

Nice — we now have a button that halves the opacity and swaps the label over when it's clicked. You could add a loading spinner here in place of the text, combine the two, change the color of the button with an additional class, animate it... you get it, pretty much anything you need to do.

Handling multiple forms on a page

If you're dealing with multiple forms or actions on a page, wire:loading doesn't care and will just trigger any component loading event. Basically, wire:loading isn't locally scoped to the component. This means that by submitting one form or action, all your other loading states will trigger.

To get around this, you'll need to specify which action is responsible for each loading state.

Here's an example of two forms, which both call different actions, and the solution by targeting each action.

<div>
    <form wire:submit="login">
        {{-- Form stuff --}}
        <button wire:loading.class="opacity-50" wire:target="login">Log in</button>
    </form>

    <form wire:submit="register">
        {{-- Form stuff --}}
        <button wire:loading.class="opacity-50" wire:target="register">Register</button>
    </form>
</div>

By introducing wire:target, we're now only applying the opacity-50 class (or whatever else you're doing) to trigger when that specific action is triggered.

Here's the example of using wire:target with the more complex button change we looked at earlier.

<form wire:submit="login">
    {{-- Form stuff --}}

    <button wire:loading.class="opacity-50" wire:target="login">
        <span wire:loading.remove wire:target="login">Log in</span>
        <span wire:loading wire:target="login">Logging you in...</span>
    </button>
</form>

So, we've added wire:target to every element we're using wire:loading on to ensure it's scoped to only when the login action is called.

That's pretty much it

The majority of the time, everything we've covered here will allow you to create some pretty nice UI changes when you're calling actions in Livewire. However, be sure to check out the Livewire documentation, which details some additional things if you need any more control over your loading states.

Happy loading!

Author
Alex Garrett-Smith