The issue here is that Livewire has trouble reconciling the DOM changes when you conditionally swap entire child components, especially when using the wire:key directive at multiple levels or changing components on-the-fly during the lifecycle.
Why This Error Happens
When you switch your $step, Livewire expects to find a component DOM node that corresponds to the Livewire component it knows about—but, since you completely swap out one child component for a different one, Livewire loses its reference and throws the error:
Uncaught Could not find Livewire component in DOM tree
This is especially common if Livewire's DOM-diffing expects a component that no longer exists (or its key changed).
How to Fix
1. Move Conditional Logic Inside a Single Child Component
Instead of conditionally rendering different Livewire child components, consider always rendering a single child (for the questionnaire), and putting the step logic inside it so it doesn't get swapped out.
<livewire:questionnaires.form :step="$step" wire:key="q-form-{{ $step }}" />
Then inside the Questionnaires/Form Livewire component, use:
@if($step == 1)
<!-- Step 1 content -->
@elseif($step == 2)
<!-- Step 2 content -->
@endif
2. Alternatively: Use Only One wire:key At The Top Level
If you want to keep each step as a separate component, only use the wire:key on the component itself, and not on the wrapping <div>. Remove the key from the outer div.
<div class="flex flex-col gap-4 min-h-0">
@if ($step == 1)
<livewire:questionnaires.step1 wire:key="q-step1" />
@elseif ($step == 2)
<livewire:questionnaires.step2 wire:key="q-step2" />
@elseif ($step == 3)
<livewire:questionnaires.step3 wire:key="q-step3" />
@endif
</div>
3. Do Not $refresh When Changing State
Calling $this->dispatch('$refresh') is unnecessary and can cause reactivity issues. Just update your state:
#[On('next-step')]
public function nextStep(): void
{
$this->step = $this->step + 1;
}
Summary
- Prefer a single child component for step logic (most robust).
- If you MUST conditionally swap child Livewire components, do NOT use
wire:keyon a parent<div>. - Avoid dispatching
$refreshunnecessarily. Just change the$stepproperty. - Always ensure your child components have unique keys based solely on
$step.
Final Example
Parent Blade Component:
<div class="flex flex-col gap-4 min-h-0">
@if ($step == 1)
<livewire:questionnaires.step1 wire:key="q-step1" />
@elseif ($step == 2)
<livewire:questionnaires.step2 wire:key="q-step2" />
@elseif ($step == 3)
<livewire:questionnaires.step3 wire:key="q-step3" />
@endif
</div>
Parent Logic:
#[On('next-step')]
public function nextStep(): void
{
$this->step++;
}
Further Reading
If you need to preserve state in each child component across swaps, you may need a more advanced state management strategy. For a typical step-based workflow, the above pattern should work smoothly!