Laravel Livewire: Dashboard with Auto-Refresh "Next Page" Every 5 Seconds

Laravel Livewire: Dashboard with Auto-Refresh "Next Page" Every 5 Seconds
Admin
Wednesday, June 28, 2023 7 mins to read
Share
Laravel Livewire: Dashboard with Auto-Refresh "Next Page" Every 5 Seconds

In this tutorial, we will create a dashboard that will automatically refresh every 5 seconds to show the next piece of data, with an example of sports leagues tables. We will use Livewire feature polling for this.

dashboard final result

The main point is to show the first page of the data, then jump to the second page after 5 seconds, then to the third, and so on, and then back to the first one. Similar to how they show sports tables on TV in the news.

To make it more realistic, we will show results from four English Football Leagues with random points for the teams.

First, we show teams no.1-5 for every league. Then it refreshes to 6-10th teams, then to 11-15th, then to 16-20th, and then back to 1-5.


Application Data Setup

For this tutorial, we will need two Models: League and Team:

php artisan make:model League -m
php artisan make:model Team -m

database/migrations/xxxx_create_leagues_table.php:

public function up(): void
{
Schema::create('leagues', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}

database/migrations/xxxx_create_teams_table.php:

public function up(): void
{
Schema::create('teams', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('points');
$table->foreignId('league_id')->constrained();
$table->timestamps();
});
}

app/Models/League.php:

use Illuminate\Database\Eloquent\Relations\HasMany;
 
class League extends Model
{
protected $fillable = [
'name',
];
 
public function teams(): HasMany
{
return $this->hasMany(Team::class);
}
}

app/Models/Team.php:

class Team extends Model
{
protected $fillable = [
'name',
'points',
'league_id',
];
}

For this example, we will use one of the themes from our package Larastarters:

composer require laraveldaily/larastarters --dev
php artisan larastarters:install

I chose the Laravel Breeze (Tailwind) option and the Windmill theme, but you can use whichever you like.


Install Laravel Livewire

We need to add Livewire and create a component.

composer require livewire/livewire

resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html x-data="data" lang="en">
<head>
// ...
 
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
<!-- Scripts -->
<script src="{{ asset('js/init-alpine.js') }}"></script>
</head>
<body>
// ...
 
@livewireScripts
</body>
</html>

For the Livewire Component, let's simply name it Dashboard.

php artisan make:livewire Dashboard

resources/views/dashboard.blade.php:

<x-app-layout>
<x-slot name="header">
{{ __('Dashboard') }}
</x-slot>
 
<div class="p-4 bg-white rounded-lg shadow-xs">
{{ __('You are logged in!') }}
@livewire('dashboard')
</div>
</x-app-layout>

Showing Teams

We will build the dynamic functionality step by step.

The first milestone objective is to show all the teams in the tables.

When the Component gets mounted, first, we need to assign the default values in the mount() method. We need to get all the leagues, and we will pluck it to have a result as ID => name. For the teams, first, we will make an empty Collection.

app/Http/Livewire/Dashboard.php:

use App\Models\League;
use App\Models\Team;
use Illuminate\Support\Collection;
use Illuminate\Contracts\View\View;
 
class Dashboard extends Component
{
public Collection $teams;
public Collection $leagues;
 
public function mount(): void
{
$this->leagues = League::pluck('name', 'id');
$this->teams = collect();
}
 
public function render(): View
{
return view('livewire.dashboard');
}
}

Next, to get the data for the $teams, we will create a separate method called getLeagueResults() because we will call it in a few places. For starters, we call it at the end of the mount() method.

app/Http/Livewire/Dashboard.php:

class Dashboard extends Component
{
public Collection $teams;
public Collection $leagues;
 
public function mount(): void
{
$this->leagues = League::pluck('name', 'id');
$this->teams = collect();
 
$this->getLeagueResults();
}
 
// ...
 
private function getLeagueResults(): void
{
foreach ($this->leagues as $leagueId => $league) {
$this->teams[$leagueId] = Team::where('league_id', $leagueId)
->orderByDesc('points')
->get();
}
}
}

Now let's show them in the table. In the Livewire component, the public property $this->teams can be accessible in the Blade file automatically.

resources/views/livewire/dashboard.blade.php:

<div>
<div class="grid gap-6 mb-8 md:grid-cols-4">
@foreach ($teams as $leagueId => $leagueTeams)
<div class="p-1 bg-white rounded-lg shadow-xs">
<h2 class="text-2xl mb-4">{{ $leagues[$leagueId] }}</h2>
<div class="overflow-hidden mb-8 w-full rounded-lg border shadow-xs">
<div class="overflow-x-auto w-full">
<table class="w-full whitespace-no-wrap">
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase bg-gray-50 border-b">
<th class="px-4 py-3 w-3/4">Team</th>
<th class="px-4 py-3 text-center">Points</th>
</tr>
</thead>
<tbody class="bg-white divide-y">
@foreach($leagueTeams as $team)
<tr class="text-gray-700">
<td class="px-4 py-3 text-sm">
{{ $loop->iteration }}.
{{ $team->name }}
</td>
<td class="px-4 py-3 text-sm text-center">
{{ $team->points }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endforeach
</div>
</div>

As you can see, we also use $leagues[$leagueId] from the public property $this->leagues to get and show the league's name.

For now, we should see a complete list of teams for every league.


Paginate and Live Update

Now that we can view all the teams let's add pagination to them and update the table after 5s. In this example, we won't directly use Laravel or Livewire built-in pagination but instead build the queries manually, as we don't need pagination links in this example.

So, first, the public properties. We will need four of them. I guess their names are self-explanatory:

  • $teamsPerPage
  • $totalPages
  • $currentPage
  • $currentOffset

app/Http/Livewire/Dashboard.php:

class Dashboard extends Component
{
public Collection $teams;
public Collection $leagues;
 
public int $teamsPerPage = 5;
 
public int $totalPages = 0;
public int $currentPage = 1;
public int $currentOffset = 0;
 
// ...
}

Next, in the mount() method, we need to calculate how many total pages we will have.

app/Http/Livewire/Dashboard.php:

class Dashboard extends Component
{
// ...
 
public function mount(): void
{
$this->leagues = League::pluck('name', 'id');
$this->teams = collect();
$maxTeamsPerLeague = League::withCount('teams')
->orderBy('teams_count', 'desc')
->value('teams_count');
$this->totalPages = ceil($maxTeamsPerLeague / $this->teamsPerPage);
 
$this->getLeagueResults();
}
 
// ...
}

And when setting the $teams property, we need to take records for how many are set in the $teamsPerPage property and offset with the $currentOffset property.

app/Http/Livewire/Dashboard.php:

class Dashboard extends Component
{
// ...
 
private function getLeagueResults(): void
{
foreach ($this->leagues as $leagueId => $league) {
$this->teams[$leagueId] = Team::where('league_id', $leagueId)
->orderByDesc('points')
->take($this->teamsPerPage)
->offset($this->currentOffset)
->get();
}
}
}

Now we should see a table with only five teams per league.


Auto-Refresh with Polling

And lastly, let's add polling for auto-refresh. First, we will add the wire:poll directive, set the frequency to 5s, and call a specific method called update.

resources/views/livewire/dashboard.blade.php:

<div>
<div wire:poll.5s="update">
// ...
</div>

Now we can add the logic for the polling, creating that method.

app/Http/Livewire/Dashboard.php:

class Dashboard extends Component
{
// ...
 
public function update(): void
{
$this->currentPage++;
if ($this->currentPage > $this->totalPages) {
$this->currentPage = 1;
}
$this->currentOffset = ($this->currentPage - 1) * $this->teamsPerPage;
 
$this->getLeagueResults();
}
 
// ...
}

What are we doing here? First, increasing the currentPage. Then we need to check if the currentPage isn't bigger than there are total pages. Otherwise, it will break the dashboard. If we show the last page, we go to the first page again.

Next, we must calculate the currentOffset and re-fetch the team results.

Finally, we need to add the offset to the number of the team to show the correct place.

resources/views/livewire/dashboard.blade.php:

// ...
@foreach($leagueTeams as $team)
<tr class="text-gray-700">
<td class="px-4 py-3 text-sm">
{{ $loop->iteration }}.
{{ $currentOffset + $loop->iteration }}.
{{ $team->name }}
</td>
<td class="px-4 py-3 text-sm text-center">
{{ $team->points }}
</td>
</tr>
@endforeach
 
// ...

Perfect! Now we have the result as expected.