To give the user a responsive UI, a loading indicator, a cancel button, and a hard timeout while cleaning up resources, follow these steps in a Filament resource or page Livewire component:
- Declare the action as cancelable and set a timeout (Filament v3 syntax shown; see note below for v2):
use Filament\Actions\Action;
public function longRunningAction(): Action
{
return Action::make('process')
->label('Start long process')
->action(fn () => $this->runProcess())
->timeout(30) // seconds – hard timeout enforced by Livewire
->cancelable() // renders a cancel button in modals/button slots
->modal() // optional: show in a modal with loading spinner
->requiresConfirmation() // optional: ask before starting
->color('primary');
}
- Implement the action method with periodic cancellation checks. The method receives a $livewire instance that you can use to test for cancellation:
public function runProcess(): void
{
$this->setLoading(); // show spinner / disable UI
try {
for ($i = 0; $i < 100; $i++) {
// Simulate work
sleep(1);
// If the user clicked cancel, Livewire sets $this->isCancelled()
if ($this->isCancelled()) {
$this->cleanup();
$this->setState('cancelled');
return;
}
// Optional: report progress
$this->dispatch('progress-update', ['step' => $i+1]);
}
$this->setState('success');
} catch (\Throwable $e) {
// Livewire will also throw a TimeoutException when the timeout elapses
$this->cleanup();
$this->setState('timed-out');
} finally {
$this->resetLoading();
}
}
- Add lifecycle hooks to handle the three outcomes:
protected function cancelled()
{
// Runs when the user clicks the cancel button
$this->cleanup();
$this->setState('cancelled');
}
protected function handleTimeoutException(\Livewire\Exceptions\TimeoutException $e)
{
// Runs when the per‑action timeout is exceeded
$this->cleanup();
$this->setState('timed-out');
}
private function setState(string $state): void
{
$this->state = $state;
$this->dispatch('state-changed', ['state' => $state]);
}
private function resetLoading(): void
{
$this->dispatch('loading-end');
}
private function setLoading(): void
{
$this->dispatch('loading-start');
}
private function cleanup(): void
{
// Stop any background workers, queues, or timers you started
// Example: if you dispatched a job, you could delete it or signal it to stop
// $this->job->delete();
}
- Update the view to react to the dispatched events (Blade example):
Start process
Working…
Cancel
Completed successfully.
Cancelled by user.
Operation timed out.
Likely explanation vs. confirmed facts
Confirmed facts (from Filament/Livewire behavior):
- Filament v3 provides the fluent
->timeout(int $seconds) and ->cancelable() methods on Filament\Actions\Action.
- When
->cancelable() is used, Livewire adds a cancel button and fires the component’s cancelled() hook.
- If the action exceeds the timeout, Livewire throws a
Livewire\Exceptions\TimeoutException, which can be caught via handleTimeoutException().
- All timeout and cancellation logic runs on the Livewire request lifecycle, so any PHP code in the action method is halted as soon as the limit is reached.
Likely explanation (assumptions that need verification):
- The
isCancelled() method (or checking $this->isCancelled()) is available on the component when the action is cancelable; if not, you can inspect a custom flag set in the cancelled() hook.
- Background workers or queued jobs must be designed to respect a cancellation signal (e.g., checking a cache flag or database column) because Livewire’s timeout only aborts the current HTTP request, not external processes.
- Using
->modal() automatically closes the modal on timeout or cancel, but you may still need to reset any local loading state manually.
Missing diagnostic detail
To give a version‑specific recommendation, please confirm which Filament version you are using (v2 or v3). The per‑action ->timeout() and ->cancelable() APIs exist only in Filament v3; v2 relies on Livewire’s global wire:timeout attribute and manual cancel handling.