Filament is a great admin panel for Laravel built with Livewire, one of its features is creating dashboard widgets to get quick stats of your application.
One issue we were having is that some of the widgets could take up to a second to load all the data needed. If you're just visiting the dashboard, you don't want to wait on all the stats just because one of them is loading slowly.
With Livewire, you can easily defer loading of something until the page was visited. We'll be applying this concept to the "Stats overview widget" of Filament. You can apply this to any other widget as well.
First off, we'll create our widget, which we'll call "SlowWidget". When Filament asks for a resource, just press <Enter>
php artisan make:filament-widget SlowWidget --stats-overview
This gives us the following PHP class:
<?php namespace App\Filament\Widgets; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Card; class SlowWidget extends BaseWidget { protected function getCards(): array { return [ // ]; } }
Now we'll simulate slow retrieval of data by adding a small sleep()
call inside the getCards()
method.
<?php namespace App\Filament\Widgets; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Card; class SlowWidget extends BaseWidget { protected function getCards(): array { + sleep(5); return [ + Card::make('Total', 10), ]; } }
You'll notice that loading the dashboard now takes 5 seconds before you get to see anything.
Adding Livewire's defer loading to our widget is just a few lines of extra code. First we'll define the loadData()
method and $readyToLoad
property on our widget.
<?php namespace App\Filament\Widgets; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Card; class SlowWidget extends BaseWidget { + public bool $readyToLoad = false; + public function loadData() + { + $this->readyToLoad = true; + } protected function getCards(): array { sleep(5); return [ Card::make('Total', 10), ]; } }
We'll need to change our getCards()
method to take into account that we're ready to load the data or not:
<?php namespace App\Filament\Widgets; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Card; class SlowWidget extends BaseWidget { public bool $readyToLoad = false; public function loadData() { $this->readyToLoad = true; } protected function getCards(): array { + if (! $this->readyToLoad) { + return [ + Card::make('Total', 'loading...'), + ]; + } sleep (5); return [ Card::make('Total', 10), ]; } }
The dashboard should now be fast again, but you'll notice that the widget now stays stuck on "loading...", even if you wait for 5 seconds or longer for it to load.
The last step we need is to override the stats-overview-widget.blade.php
of Filament to have it call our loadData()
method, you can publish the Filament views, or just create the file at resources/views/vendor/filament/widgets/stats-overview-widget.blade.php
By default it looks like this:
<x-filament::widget class="filament-stats-overview-widget"> <div {!! ($pollingInterval = $this->getPollingInterval()) ? "wire:poll.{$pollingInterval}" : '' !!}> <x-filament::stats :columns="$this->getColumns()"> @foreach ($this->getCachedCards() as $card) {{ $card }} @endforeach </x-filament::stats> </div> </x-filament::widget>
We'll make one small addition, note the wire:init="loadData"
add the end of the x-filament:widget
tag:
+ <x-filament::widget class="filament-stats-overview-widget" wire:init="loadData"> <div {!! ($pollingInterval = $this->getPollingInterval()) ? "wire:poll.{$pollingInterval}" : '' !!}> <x-filament::stats :columns="$this->getColumns()"> @foreach ($this->getCachedCards() as $card) {{ $card }} @endforeach </x-filament::stats> </div> </x-filament::widget>
And that's it, the widget now displays 10
as the value after 5 seconds, but your dashboard stays snappy!