Keeping Livewire Components Fresh with wire:poll
Livewire components go stale between user interactions. wire:poll fixes that with one attribute — here's how it works, a job-status example, and when to stop polling.
25 Oct 2025, 23:42 UTC

The stale dashboard problem
You build a Livewire component that shows a background job's progress. It renders perfectly on page load — then sits there, frozen, until the user clicks something. The job finished two minutes ago, but the UI still says "Processing…" because Livewire only re-renders when the user interacts with it.
The useful takeaway: wire:poll solves this with a single HTML attribute. It tells Livewire to re-request the component on a fixed interval, so server-side state stays visible without websockets, custom JavaScript, or full page reloads.
What wire:poll actually does
Adding wire:poll to an element in your Blade template makes Livewire send a standard component update request every few seconds (the default interval is 2 seconds; wire:poll.5s sets five seconds). On each request, the component re-renders on the server and Livewire's DOM diffing morphs only the parts of the HTML that changed. Nothing else on the page is touched.
Two details matter here. First, every poll is a full Livewire round trip — the component hydrates, renders, and dehydrates — so polling isn't free. Second, by default polling pauses when the browser tab is in the background, which saves server load but means the data may be stale the moment a user returns to the tab. If you need updates even in background tabs, add the .keep-alive modifier.
A worked example: polling a job status
Assume Laravel 11 with Livewire 3 installed (composer require livewire/livewire). Create the component with php artisan make:livewire JobStatus.
// app/Livewire/JobStatus.php
class JobStatus extends Component
{
public int $jobId;
public function render()
{
$job = Job::findOrFail($this->jobId);
return view('livewire.job-status', [
'status' => $job->status,
'progress' => $job->progress,
]);
}
}Because render() runs on every poll, fetching fresh data there is the simplest correct pattern — no extra method needed.
{{-- resources/views/livewire/job-status.blade.php --}}
<div wire:poll.5s>
<p>Status: {{ $status }}</p>
<progress value="{{ $progress }}" max="100"></progress>
</div>To verify it works: open the page, open your browser's network tab, and filter for requests to /livewire/update. You should see one POST request roughly every five seconds, and the status text should change as the job progresses — with no page reload. If you change the interval to wire:poll.10s, the request frequency should halve. Don't take my word for the timing; confirm it against your own interval.
Stopping the poll when the work is done
Polling forever wastes requests. A clean pattern is conditional polling: only emit the attribute while the job is active.
<div @if($status !== 'finished') wire:poll.5s @endif>
...
</div>Once the server renders the component with a finished status, the attribute disappears and polling stops on the next client update. This is the single most effective way to keep polling cheap.
Trade-offs worth knowing
- Traffic scales with users. Each poll is a full Livewire request. One hundred open tabs at a 2-second interval is 50 requests per second against one component. Raise the interval, poll only visible components, and stop polling when data is final.
- Freshness is bounded by the interval. With a 5-second poll, updates can be up to 5 seconds late. For genuinely urgent changes — a payment completing, a chat message — Livewire events or Laravel Echo with websockets are a better fit. Polling and events combine well: poll as a fallback, push for the urgent path.
- Background tabs pause by default. Decide deliberately whether
.keep-aliveis worth the extra load.
Closing
For low-frequency server state — job progress, queue counts, a "last synced" timestamp — wire:poll is the cheapest correct solution in the Livewire toolbox. Add the attribute, fetch fresh data in render(), make polling conditional, then open the network tab and tune the interval against real traffic in your own environment. If you find yourself pushing the interval below a couple of seconds, that's your signal to reach for websockets instead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.