Refactoring Livewire Loading Indicators for a Smoother UX
Ever had a loading indicator that flashes annoyingly, appearing and disappearing seemingly at random? We did, in the Reimpact/platform project. A previously implemented wire:loading approach for our charts was causing more frustration than help.
The Problem
The original implementation relied on wire:loading which, while convenient, proved unreliable for complex interactions. Specifically, the loading indicators on our charts were flickering unpredictably, especially when filters or product selections triggered Livewire updates. This was largely because the loading state wasn't consistently tied to the specific events that initiated data fetching. The user experience suffered from these jarring visual cues.
The Solution: Event-Driven Loading with Alpine.js and Filament Design
To address this, we refactored the loading indicator to be event-driven, leveraging Alpine.js for DOM manipulation and adopting Filament's native design for visual consistency. Here’s the breakdown:
- Event Dispatch: The "Apply" button (for filters) and the product selector now dispatch custom browser events before the Livewire request is initiated.
- Alpine.js Listener: The chart component listens for these browser events using
@syntax in Alpine.js. - Loading Indicator Control: Upon receiving the event, Alpine.js shows the Filament's native
fi-loading-indicatorSVG, complete withanimate-spinfor a visually appealing loading state. Once theupdateChartDataLivewire method completes and updates the chart, the loading indicator is hidden. - CSS Transitions: Smooth opacity transitions were added to prevent abrupt appearances and disappearances, providing a more polished feel.
Here’s an illustrative example of how you might implement this in your Livewire component's Blade view:
<div x-data="{ isLoading: false }"
@custom-loading.window="isLoading = true"
@chart-updated.window="isLoading = false">
<button wire:click="applyFilters" @click="$dispatch('custom-loading')">Apply Filters</button>
<div x-show="isLoading" class="fi-loading-indicator animate-spin">
<!-- Filament loading indicator SVG here -->
Loading...
</div>
<!-- Chart content here -->
</div>
In this example:
custom-loadingis the event dispatched beforeapplyFilterstriggers the Livewire update.chart-updatedis an event dispatched after the chart has been updatedisLoadingis a boolean Alpine.js property that controls the visibility of the loading indicator.fi-loading-indicatoris a placeholder for the actual Filament loading indicator SVG.
The Benefits
This approach offers several advantages:
- Reliability: The loading indicator is now directly tied to specific user actions, eliminating random flickering.
- Visual Consistency: Using Filament's native design ensures a unified look and feel across the application.
- Maintainability: The code is more readable and easier to maintain, as the loading logic is clearly defined within the Alpine.js component.
The Takeaway
Don't rely solely on implicit Livewire directives for complex UI interactions. Consider an event-driven approach with Alpine.js to gain more control and create a smoother, more predictable user experience. By dispatching custom events and listening for them in your components, you can orchestrate complex loading sequences with ease. Also, remember to leverage the UI library you are using (in this case, Filament) and make your loading indicators consistent with the rest of the design.