Managing Loading States in Livewire 3 with Alpine.js

nasrulhazim

Nasrul Hazim Bin Mohamad

Posted on August 31, 2024

Managing Loading States in Livewire 3 with Alpine.js

Managing loading states in a Livewire and Alpine.js application can be tricky, especially when you want both flexibility and reliability. In this post, I'll show you how to set up a robust loading state management system using Alpine.js, Livewire 3, and some custom event handling. This method ensures that your UI remains responsive and user-friendly, no matter what actions users take.

Introduction

When building dynamic web applications, it's crucial to provide users with visual feedback when actions are being processed. Loading indicators are a great way to let users know that something is happening in the background. However, handling these indicators can become complicated, especially when you need them to appear and disappear at the right times.

In this guide, we'll walk through setting up a loading indicator that can be controlled both manually and automatically, using Livewire 3 and Alpine.js.

The Problem

You might have a Livewire component with a save button like this:

<x-button class="ml-3" wire:click="save" wire:loading.attr="disabled">
    {{ __('Save') }}
</x-button>
Enter fullscreen mode Exit fullscreen mode

This setup automatically disables the button when a request is in progress, but it doesn't handle the loading state as comprehensively as you might need. You want a solution that:

  • Allows manual control over when the loading state is triggered and stopped.
  • Automatically stops the loading state when any request is completed, whether it succeeds or fails.

The Solution

The solution involves using Alpine.js to manage the loading state and Livewire 3's dispatch method to trigger the loading state.

Step 1: Set Up Alpine.js

First, define an Alpine.js component that manages the loading state:

Alpine.data('loadingIndicator', () => ({
    loading: false,
    init() {
        // Listen for custom events to control the loading state
        Livewire.on('loading', () => {
            this.loading = true;
        });

        Livewire.on('unloading', () => {
            this.loading = false;
        });

        // Automatically turn off loading after any request
        Livewire.hook('request', ({ respond, succeed, fail }) => {
            respond(() => {
                this.loading = false;
            });

            succeed(() => {
                this.loading = false;
            });

            fail(({ preventDefault }) => {
                this.loading = false;
                preventDefault(); // Optional: Prevent default error handling
            });
        });
    }
}));
Enter fullscreen mode Exit fullscreen mode

Step 2: Setup Loading Component

I have the following <x-loading /> component defined:

@props(['message' => 'loading...', 'target' => null])

<div x-data="loadingIndicator" x-show="loading" x-cloak x-on:loading="loading = true" x-on:unloading="loading = false"
    class="fixed inset-0 flex items-center justify-center bg-gray-100 bg-opacity-75 z-100">
    <div class="flex mx-auto space-x-4">
        <x-icon name="loader"></x-icon>
        <span>{{ __($message) }}</span>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Add this component in your main layout:

<x-loading />
Enter fullscreen mode Exit fullscreen mode

Step 3: Trigger the Loading State Manually

You can trigger the loading state manually by dispatching custom events. Here’s how you can do it in your Blade template:

<x-button class="ml-3" 
    wire:click="$dispatch('loading'); $wire.save()" 
    wire:loading.attr="disabled">
    @lang('Save')
</x-button>
Enter fullscreen mode Exit fullscreen mode

You should have something like the following:

Displaying the Loading State

You may design your own loading state.

Step 4: Automatically Stop the Loading State

Livewire's request hook ensures that the loading state is turned off after any request is completed:

  • respond: Turns off the loading state when the server responds.
  • succeed: Turns off the loading state when the request is successful.
  • fail: Turns off the loading state when the request fails.

Step 5: Handling Errors Gracefully

If a request fails, you might want to handle it gracefully. The fail callback allows you to manage errors and even prevent default error handling:

fail(({ preventDefault }) => {
    this.loading = false;
    preventDefault(); // Prevent default error handling if needed
});
Enter fullscreen mode Exit fullscreen mode

Step 6: Dispatching Events from Livewire Components

If you need to control the loading state directly from your Livewire component, you can dispatch events like this:

public function save()
{
    // Your save logic here

    // Dispatch the unloading event to stop the loading indicator
    $this->dispatch('unloading');
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

By combining Alpine.js and Livewire 3's dispatch method, you can create a flexible and reliable system for managing loading states in your application. This approach ensures that your UI always provides the right feedback to users, making your application more responsive and user-friendly.

Whether you're manually controlling the loading state or relying on automatic hooks, this setup gives you the best of both worlds. Try implementing it in your next project and experience the difference!

💖 💪 🙅 🚩
nasrulhazim
Nasrul Hazim Bin Mohamad

Posted on August 31, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related