How to Refresh Widgets When Table Actions Are Fired
In this article, we will explore how to use Filament widgets to create statistic cards that display user statistics about the user table. We will also see how to refresh the widgets when the user table is changed, using Livewire lifecycle hooks and events.
To get started with the Filament, you can install a new Laravel project called filament-widget-examples:
laravel new filament-widget-examples
Now you can install Filament using the command:
cd filament-widget-examplescomposer require filament/filament
Filament recommends adding this to your composer.json's post-update-cmd:
"post-update-cmd": [ // ... "@php artisan filament:upgrade"]
Edit the User Migration File:
Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->boolean('is_admin'); $table->boolean('is_active'); $table->rememberToken(); $table->timestamps();});
Edit the User Factory definition method:
//database\factories\UserFactory.phppublic function definition(){ return [ 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), 'is_admin' => fake()->boolean(), 'is_active' => fake()->boolean(), ];}
Edit the DatabaseSeeder run method:
//database\seeders\DatabaseSeeder.phppublic function run(){ \App\Models\User::factory(100)->create();}
Edit the User Model:
//app/Models/User.phpprotected $fillable = [ 'name', 'email', 'password', 'is_active', 'is_admin'];
Run the migrate command:
php artisan migrate --seed
You can create a User Resource and a Widget:
php artisan make:filament-resource User --simplephp artisan make:filament-widget UserOverview --resource=UserResource --stats-overview
Edit the User Resource file:
Form:
//app/Filament/Resources/UserResource.phppublic static function form(Form $form): Form{ return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\TextInput::make('email') ->email() ->required() ->maxLength(255), Forms\Components\Toggle::make('is_admin'), Forms\Components\Toggle::make('is_active'), ]);}
Table:
//app/Filament/Resources/UserResource.phppublic static function table(Table $table): Table{ return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('email'), Tables\Columns\IconColumn::make('is_admin') ->boolean(), Tables\Columns\IconColumn::make('is_active') ->boolean(), Tables\Columns\TextColumn::make('created_at') ->date(), Tables\Columns\TextColumn::make('updated_at') ->date(), ]) ->filters([ Tables\Filters\TernaryFilter::make('is_admin'), Tables\Filters\TernaryFilter::make('is_active'), ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]);}
Add the getWidgets method:
//app/Filament/Resources/UserResource.phpuse App\Filament\Resources\UserResource\Widgets\UserOverview;public static function getWidgets(): array{ return [ UserOverview::class, ];}
Add the getHeaderWidgets method:
//app/Filament/Resources/UserResource/Pages/ManageUsers.phpprotected function getHeaderWidgets(): array{ return [ UserOverview::class, ];}
You can use the Widget cards to display a number of different stats in a single widget. Edit the Users Overview Widget to this:
//app/Filament/Resources/UserResource/Widgets/UserOverview.phpclass UserOverview extends BaseWidget{ protected static ?string $pollingInterval = null; protected function getCards(): array { $usersCount = User::selectRaw(' COUNT(*) as total, SUM(CASE WHEN is_admin THEN 1 ELSE 0 END) AS admin, SUM(CASE WHEN is_active THEN 1 ELSE 0 END) AS active ')->first(); return [ Card::make('Total', $usersCount->total) ->color('primary') ->description('Total users'), Card::make('Admin', $usersCount->admin) ->color('danger') ->description('Admin users'), Card::make('Active', $usersCount->active) ->color('success') ->description('Active users'), ]; }}
Check the User Resource on your browser. We have three header widgets: Total users, Admin users and Active users.
You can delete users in the table, but these widgets won't be updated, only the table. You need to refresh the page to update the widgets.
Each Livewire component undergoes a lifecycle. Lifecycle hooks allow you to run code at any part of the component's lifecyle. We can use the Updated hook to runs after any update to the Livewire component's data.
Listeners are a key->value pair where the key is the event to listen for, and the value is the method to call on the component.
We can use the $refresh magic action to re-render the component without firing any action.
Let's add a listener in the Users Overview Widget file:
//app/Filament/Resources/UserResource/Widgets/UserOverview.phpprotected $listeners = ['updateUserOverview' => '$refresh'];
The updated hook method provides two attributes:
We are going to use the $name attribute to listen to two table events:
Then, we can check these names in the updated method and emit the updateUserOverview event:
//app/Filament/Resources/UserResource/Pages/ManageUsers.phppublic function updated($name){ if (Str::of($name)->contains(['mountedTableAction', 'mountedTableBulkAction'])) { $this->emit('updateUserOverview'); }}
Now, the widgets will always be updated when the delete action is clicked.
You can also check other table events, for example:
//text input in the top right of the tableif (Str::of($name)->contains('tableSearchQuery')) { ...} //the table filter formif (Str::of($name)->contains('tableFilters')) { ...}
This project is available on Github: https://github.com/leandrocfe/filament-widget-examples
I hope you enjoy it :)
Very well written. Thank you, Leandro!
Thank you!
Nice one!
I'm trying to refresh a widget when the table filters are reset, do you know of a way? Thank you!
For anyone struggling with this issue, i solved it by using resetPage() instead of updated(), like this
Great! You can also use the resetTableFiltersForm() method, if you want
public function resetTableFiltersForm(): void { $this->getTableFiltersForm()->fill(); $this->updatedTableFilters(); $this->emit('updateUserOverview'); }
This is great!
But what if I want to access the current filter and update the widget based on this? Is there a way to do it?
yes,
updated($name, $value)
$name will be 'tableFilters' $value you can get the current filter data
Thanks a tons! I spent all day wondering and was almost about to scrap the idea altogether.
Still stuck, unfortunately. I could fetch the value in updated(), but i want that value to be avaialble in getViewData() of my Widget.
you can send $this->emit('updateUserOverview', $value)
check the livewire
Thank you ! Works!