The problem: A Livewire-powered list works fine until you add, remove, or reorder items — then the browser console throws a snapshot error and the UI gets visibly out of sync with your data.
Environment: Laravel 11.x/13.x, Livewire 3.x/4.x, Blade templates using @foreach loops inside a Livewire component.
The Error
Error: Snapshot missing on Livewire component with id: a1b2c3d4
// thrown the moment Livewire tries to reconcile the DOM after
// an update — it can no longer match up which rendered element
// corresponds to which item in the updated data
Why It Happens
Livewire doesn't re-render your whole page on every update — it diffs the new HTML against the old HTML and patches only what changed. To do that reliably inside a loop, it needs a stable identity for each item, the same way JavaScript frameworks need a key prop in a list. Without one, Livewire falls back to matching elements by their position in the DOM, and that breaks the instant the list order or length changes.
This shows up in a few closely related situations:
- A plain
@foreachloop rendering Blade markup (not components) with nowire:keyon the root element of each iteration. - A
@foreachloop rendering nested Livewire components, where the key is missing on the<livewire:component />tag itself — this one is easy to miss because the loop markup around it looks fine. - Duplicate keys within the same render — commonly from using a model's
iddirectly as the key when two different model types (like aPostand anAuthor) can share the same numeric ID. - A
@switch/@caseblock inside a loop where each case needs its own key, not just the outer loop.
The Fix
Add wire:key to the opening tag of the first element inside any loop:
@foreach ($posts as $post)
<div class="post-card">
{{ $post->title }}
</div>
@endforeach
@foreach ($posts as $post)
<div wire:key="post-{{ $post->id }}" class="post-card">
{{ $post->title }}
</div>
@endforeach
Nested Livewire components need the same treatment, directly on the component tag — Blade elements around it don't count:
@foreach ($posts as $post)
<livewire:post-card :post="$post" wire:key="post-card-{{ $post->id }}" />
@endforeach
Prefix keys when two different models could collide on the same ID — never rely on a raw id alone if more than one model type appears in overlapping loops on the same page:
wire:key="{{ $post->id }}" // collides if an $author shares this ID
wire:key="post-{{ $post->id }}"
wire:key="author-{{ $author->id }}"
Step-by-Step
- Find every
@foreach(and@switch/@case) loop in the component's Blade view that renders more than a single static element. - Add
wire:keyto the very first element inside the loop body — not a child element further down, the opening tag itself. - For any nested
<livewire:component />tags inside a loop, addwire:keydirectly on that tag, even if the surrounding Blade markup already has one. - Build each key from a prefix plus the model's ID (e.g.
post-{{ $post->id }}) rather than the bare ID, so different model types can never collide. - If you have nested loops (a loop inside a loop), make sure both levels have their own distinct keys — an inner loop needs its own identity independent of the outer one.
- Refresh, then test the actual interaction that triggers the error — adding, removing, filtering, or reordering the list — since a static page load often won't reveal the bug at all.
wire:key values everywhere and still see odd behavior — components not updating, or two nested components seeming to swap state — check whether you have two versions of Alpine.js running on the page at once. Livewire bundles its own Alpine internally, so adding Livewire to an app that already loads Alpine separately (common when adding Livewire to an existing Breeze + Alpine app) causes conflicts that can look like a keying problem but are actually a duplicate-Alpine-instance problem, fixed by removing the extra Alpine import or CDN script entirely.
Still seeing snapshot errors after keying every loop? Share your Blade view on the Contact page and I'll help track down the missing key.