Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

darkylmnx's avatar

Keep client-side state with Livewire without Alpine JS

So I've been having this issue for a while, and can't find a solution. When a livewire component is rerendered, all states that are not tied to input or to Alpine's scope are lost.

Here's some code to demonstrate.

In the following code, I have a counter with a state on Livewire. I have a plus and minus method to +1 or -1, which rigger rerenders.

I also made a client-side state with Alpine. I also made a client-side state with vanilla JS.

When I click "plus" with Alpine, and then "plus" with Livewire, Alpine's state is kept. When I click "plus" with vanilla JS, and then "plus" with Livewire, state is reset.

Even besides JS, I created a simple HTML input, if I fill the input, and click "plus" with Livewire, the state of the input is kept, the input is not cleared out but...

I also created a Details HTML tag which natively can create a toggle system. By default I set the Details as "open", if I close it, and click "plus" with Livewire, the state is reset, and the Details become "open" again.

Basically, Livewire never reset the state if he see's Alpine or an Input but for other stuff, he does. Which could be tricky when using web components that keep internal states for example.

Any solution? Here's the sample code:

<?php

use function Livewire\Volt\{state};

state(['nb' => 0]);

$plus = fn() => $this->nb++;
$minus = fn() => $this->nb--;

?>

<div x-init="console.log('Im initing')" x-data="{nb: $wire.nb}" class="box">
    <div class="notification title">
        {{ $nb }} - <span x-text="nb"></span> - <span id="mynb">{{ $nb }}</span>
    </div>

    <details open class="box">
        <summary class="is-clickable is-unselectable">Accordion 1</summary>
        <p>
            hello paris
        </p>
        <input type="text">
    </details>

    <div class="buttons">
        <button type="button" class="button" onclick="mynb.innerText--">
            minus vanilla
        </button>
        <button type="button" class="button" onclick="mynb.innerText++">
            plus vanilla
        </button>
    </div>

    <div class="buttons">
        <button type="button" class="button" x-on:click="nb--">
            minus alpine
        </button>
        <button type="button" class="button" x-on:click="nb++">
            plus alpine
        </button>
    </div>

    <div class="buttons">
        <button type="button" class="button" wire:click="minus">
            minus
        </button>
        <button type="button" class="button" wire:click="plus">
            plus
        </button>
    </div>
</div>
0 likes
2 replies
LaryAI's avatar
Level 58

To maintain client-side state with Livewire without using Alpine.js, you can leverage Livewire's wire:ignore directive. This directive tells Livewire to ignore a specific part of the DOM when it performs updates, which can help preserve the state of elements that are managed by vanilla JavaScript or other means.

Here's how you can modify your code to use wire:ignore for the elements that you want to preserve the state for:

<?php

use function Livewire\Volt\{state};

state(['nb' => 0]);

$plus = fn() => $this->nb++;
$minus = fn() => $this->nb--;

?>

<div x-init="console.log('Im initing')" x-data="{nb: $wire.nb}" class="box">
    <div class="notification title">
        {{ $nb }} - <span x-text="nb"></span> - <span id="mynb">{{ $nb }}</span>
    </div>

    <details open class="box" wire:ignore>
        <summary class="is-clickable is-unselectable">Accordion 1</summary>
        <p>
            hello paris
        </p>
        <input type="text">
    </details>

    <div class="buttons" wire:ignore>
        <button type="button" class="button" onclick="mynb.innerText--">
            minus vanilla
        </button>
        <button type="button" class="button" onclick="mynb.innerText++">
            plus vanilla
        </button>
    </div>

    <div class="buttons">
        <button type="button" class="button" x-on:click="nb--">
            minus alpine
        </button>
        <button type="button" class="button" x-on:click="nb++">
            plus alpine
        </button>
    </div>

    <div class="buttons">
        <button type="button" class="button" wire:click="minus">
            minus
        </button>
        <button type="button" class="button" wire:click="plus">
            plus
        </button>
    </div>
</div>

Explanation:

  1. wire:ignore Directive: By adding wire:ignore to the details and buttons elements that are managed by vanilla JavaScript, Livewire will not re-render these parts of the DOM. This helps in preserving their state across Livewire updates.

  2. Alpine.js Elements: The elements managed by Alpine.js do not need wire:ignore because Alpine.js integrates well with Livewire and preserves its state automatically.

  3. Vanilla JavaScript Elements: The wire:ignore directive is particularly useful for elements managed by vanilla JavaScript, as it prevents Livewire from resetting their state.

This approach should help you maintain the client-side state for elements that are not directly tied to Livewire or Alpine.js.

darkylmnx's avatar

@LaryAI very good answer, you just did a little mistake, the second wire:ignore should be on the <span id="mynb">{{ $nb }}</span> not the div with the buttons class.

Please or to participate in this conversation.