Skip to main content
The table component provides lifecycle hooks and Livewire event dispatch for integration with other components and external systems.

Lifecycle Hooks

Override these methods to hook into the table’s render cycle:
MethodWhen CalledReceivesReturns
onQuerying(Builder $query)Before pipeline processingRaw Eloquent buildervoid
onQueried(LengthAwarePaginator $rows)After pipeline processingPaginated resultsvoid
onRendering(array $viewData)Before view renderView data arrayModified array
onRendered()After view renderNothingvoid

Example: Modify Query Before Processing

Add tenant scoping or other query modifications:
protected function onQuerying(Builder $query): void
{
    $query->where('tenant_id', auth()->user()->tenant_id);
}

Example: Log Results

Track how many results are returned:
protected function onQueried(LengthAwarePaginator $rows): void
{
    logger()->info("Table rendered with {$rows->total()} results");
}

Example: Add Extra View Data

Pass additional data to the view:
protected function onRendering(array $viewData): array
{
    $viewData['summary'] = $this->calculateSummary($viewData['rows']);
    return $viewData;
}
onRendering() must return the modified $viewData array.

Example: Track Performance

Measure query and render time:
private float $startTime;

protected function onQuerying(Builder $query): void
{
    $this->startTime = microtime(true);
}

protected function onRendered(): void
{
    $elapsed = microtime(true) - $this->startTime;
    logger()->debug("Table render took {$elapsed}s");
}

Livewire Event Dispatch

Enable automatic Livewire event dispatch for the table lifecycle:
protected function shouldDispatchTableEvents(): bool
{
    return true;
}
This dispatches Livewire events that other components can listen to:
EventDispatched
table-queryingBefore pipeline
table-queriedAfter pipeline
table-renderingBefore view render
table-renderedAfter view render

Listen From Another Component

use LivewireAttributesOn;

class DashboardStats extends Component
{
    #[On('table-queried')]
    public function handleTableQueried(): void
    {
        // React to table data changes
        $this->refreshStats();
    }
}

External Refresh Events

Every table automatically listens for refresh events:
EventScopeDescription
livewire-tables-refreshGlobalRefreshes all tables on the page
{tableKey}-refreshTargetedRefreshes a specific table

Global Refresh

Refresh all tables from any Livewire component:
$this->dispatch('livewire-tables-refresh');

Targeted Refresh

Refresh a specific table using its tableKey:
class UserTable extends DataTableComponent
{
    public string $tableKey = 'users';
}

// From another component:
$this->dispatch('users-refresh');

From Alpine.js

Dispatch refresh events from Alpine components:
<button @click="$dispatch('users-refresh')">
    Refresh Users
</button>

Custom Event Name

Override the default refresh event name:
class UserTable extends DataTableComponent
{
    protected string $refreshEvent = 'reload-user-data';
}
Now the table listens for reload-user-data instead of {tableKey}-refresh.
refreshTable() resets pagination to page 1 and triggers a re-render. Search, filters, and sorting are preserved.

Custom Listeners

Add custom event listeners to your table:
public function listeners(): array
{
    return [
        'user-created'  => 'refreshTable',
        'order-updated' => 'handleOrderUpdate',
    ];
}

public function handleOrderUpdate(): void
{
    $this->resetPage();
}
Custom listeners are merged with the built-in refresh listeners.

Filter Events

Every table automatically dispatches a table-filters-applied event whenever filters or search change:

Event Payload

ParameterTypeDescription
tableKeystringThe $tableKey of the table that changed
filtersarrayAssociative array of currently active filters
searchstringThe current search term
Empty or null filter values are omitted from the filters array.

Filter Value Types

Filter TypeExample Value
Text'John'
Select'active'
Select (multi)['pending', 'shipped']
Number'50'
NumberRange['min' => '100', 'max' => '500']
Date'2024-01-15'
DateRange['from' => '2024-01-01', 'to' => '2024-12-31']
MultiDate['2024-01-01', '2024-06-15']
Boolean'1' or '0'

When the Event Is Dispatched

The event fires in these methods:
  • applyFilter() — when a filter value is set or changed
  • removeFilter() — when a specific filter is removed
  • clearFilters() — when all filters are cleared
  • updatedSearch() — when the search input changes
  • clearSearch() — when the search is cleared
  • updatedTableFilters() — when wire:model bound filters change

Listening From a Parent Component

Update statistics or dependent UI based on filter changes:
use LivewireAttributesOn;
use LivewireComponent;

class DashboardPage extends Component
{
    public array $activeFilters = [];
    public array $searchTerms = [];

    #[On('table-filters-applied')]
    public function onFiltersApplied(string $tableKey, array $filters, string $search = ''): void
    {
        $this->activeFilters[$tableKey] = $filters;
        $this->searchTerms[$tableKey] = $search;
    }

    public function render()
    {
        $query = Order::query();
        $filters = $this->activeFilters['orders'] ?? [];
        $search = $this->searchTerms['orders'] ?? '';

        if ($search !== '') {
            $query->where(function ($q) use ($search) {
                $q->where('customer_name', 'LIKE', "%{$search}%")
                  ->orWhere('product_name', 'LIKE', "%{$search}%");
            });
        }

        if (! empty($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (! empty($filters['unit_price'])) {
            if (($filters['unit_price']['min'] ?? '') !== '') {
                $query->where('unit_price', '>=', (float) $filters['unit_price']['min']);
            }
            if (($filters['unit_price']['max'] ?? '') !== '') {
                $query->where('unit_price', '<=', (float) $filters['unit_price']['max']);
            }
        }

        return view('livewire.dashboard', [
            'totalOrders'     => $query->count(),
            'deliveredOrders' => (clone $query)->where('status', 'delivered')->count(),
            'revenue'         => (clone $query)->sum('total') ?? 0,
        ]);
    }
}

Listening From Alpine.js

React to filter changes in Alpine components:
<div x-data="{ filterCount: 0 }"
     @table-filters-applied.window="filterCount = Object.keys($event.detail.filters ?? {}).length">
    <span x-text="filterCount + ' filters active'"></span>
</div>

Multiple Tables

When you have multiple tables on the same page, use the tableKey to distinguish which table changed:
#[On('table-filters-applied')]
public function onFiltersApplied(string $tableKey, array $filters, string $search = ''): void
{
    $this->activeFilters[$tableKey] = $filters;
    $this->searchTerms[$tableKey] = $search;
}

public function render()
{
    // Products table stats
    $productsQuery = Product::query();
    foreach ($this->activeFilters['products'] ?? [] as $key => $value) {
        // Apply each filter...
    }

    // Orders table stats
    $ordersQuery = Order::query();
    foreach ($this->activeFilters['orders'] ?? [] as $key => $value) {
        // Apply each filter...
    }

    return view('livewire.page', [
        'totalProducts' => $productsQuery->count(),
        'totalOrders'   => $ordersQuery->count(),
    ]);
}

Complete Example: Stats Cards

Stats cards that update automatically when filters change:
class UserTable extends DataTableComponent
{
    public string $tableKey = 'users';

    public function filters(): array
    {
        return [
            Filter::make('department')
                ->select([
                    'engineering' => 'Engineering',
                    'sales'       => 'Sales',
                    'marketing'   => 'Marketing',
                ]),
            Filter::make('status')
                ->select([
                    'active'   => 'Active',
                    'inactive' => 'Inactive',
                ]),
            Filter::make('salary')
                ->numberRange()
                ->config(['min' => 0, 'max' => 200000]),
        ];
    }
}
When filters change, the stats cards instantly update to reflect only the filtered dataset.

Build docs developers (and LLMs) love