Fixed: Livewire Snapshot Missing Error (Missing wire:key)

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

▲ browser console
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:

  1. A plain @foreach loop rendering Blade markup (not components) with no wire:key on the root element of each iteration.
  2. A @foreach loop 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.
  3. Duplicate keys within the same render — commonly from using a model's id directly as the key when two different model types (like a Post and an Author) can share the same numeric ID.
  4. A @switch/@case block 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

  1. Find every @foreach (and @switch/@case) loop in the component's Blade view that renders more than a single static element.
  2. Add wire:key to the very first element inside the loop body — not a child element further down, the opening tag itself.
  3. For any nested <livewire:component /> tags inside a loop, add wire:key directly on that tag, even if the surrounding Blade markup already has one.
  4. 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.
  5. 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.
  6. 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.
⚠ Edge case: if you've added correct, unique 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.

Advertisement
Advertisement