oliver-modernmc's avatar

How to render livewire child components on the fly?

I have multiple livewire component wrapped insisde the if condition. I am able to load the initial page however the page just crashes whenever I go to the next step.

<div class="flex flex-col gap-4 min-h-0" wire:key="questionnaire-step-{{ $step }}">
    @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>

This is how the parent component looks like, nextStep is getting triggered by a dispatch from its child components named #[On('next-step')]

    #[On('next-step')]
    public function nextStep(): void
    {
        $this->dispatch('$refresh');
        $this->step = $this->step + 1;
    }

Whenever the step is changed to render another component I get this error on the console. Uncaught Could not find Livewire component in DOM tree

0 likes
7 replies
LaryAI's avatar
Level 58

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:key on a parent <div>.
  • Avoid dispatching $refresh unnecessarily. Just change the $step property.
  • 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!

oliver-modernmc's avatar

UPDATE: I change the @if @else blade to

<x-layouts.wizard :title="'On boarding - ' . ($titles[$step - 1] ?? '')">
    <div class="flex flex-col gap-4 min-h-0">
        <livewire:dynamic-component :component="'questionnaires.'.$componentMap[$step]" :key="$componentMap[$step]" />
    </div>
</x-layouts.wizard>

But still the same issue, page still crashes when $step is incremented. I have to reload the page and manually set the $step to access different component view

valentin_vranic's avatar

Hi. First thing I noticed, and probably will fix the issue too. You don't need the wire:key="questionnaire-step-{{ $step }}" in the parent div. wire:key is mostly used in loops to track the elements inside.

The second is for the livewire components call. Use :key instead of wire:key for them, thus that is the recommended approach. Like <livewire:questionnaires.step1 :key="'q-step1'" />

And the third would be, the $refresh on your nextStep method. Because you modify the step property, it will automatically re-render the view, and if there's no any other use case to force the whole component to $refresh then don't do it.

Hope this helps, Val

oliver-modernmc's avatar

My code has been updated with the following recommendations but this recommendation does not fix the existing issue I encounter which is Uncaught Could not find Livewire component in DOM tree

<x-layouts.wizard :title="'On boarding - ' . ($step ?? '')">
    <div class="flex flex-col gap-4 min-h-0">
        <livewire:dynamic-component :is="'questionnaires.'.$step" :key="$step" />
    </div>
</x-layouts.wizard>
class Questionnaire extends Component
{
    public string $step = 'step-one';
    public array $titles = ['Contact Information', 'Contract Signing', 'Payment', 'Questionnaire'];
    public array $componentMap = ['step-one', 'step-two', 'step-three', 'step-four'];

    #[On('next-step')]
    public function nextStep(): void
    {
        $currentIndex = array_search($this->step, $this->componentMap);
        $this->step = $this->componentMap[$currentIndex + 1];
    }

    public function render()
    {
        return view('livewire.questionnaires.index');
    }
}
valentin_vranic's avatar

Place here please the content of dynamic-component class and view too. And the x-layouts.wizard content.

Please or to participate in this conversation.