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

RayC's avatar
Level 10

Alpine timer subtracting by 2 instead of 1.

I have an auto logout Livewire component (client requested), that will show a modal one the timer has reached 10 minutes (only for testing), problem I am running into is the timer is subtracting 2 instead of 1 and I cannot figure out why. any help would be greatly appreciated.

Using Laravel 10 Livewire v3 Alpine v3

Timer currently set to 600 seconds and modal set top show at 560 seconds for testing.

Livewire component:

<?php

namespace App\Livewire;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class AutoLogout extends Component
{
    public int $timer = 600;

    protected $listeners = ['resetTimer' => 'resetTimer'];

    public function resetTimer(): void
    {
        $this->timer = 600;
    }

    public function logout()
    {
        Auth::logout();
        return redirect()->route('login');
    }

    public function render()
    {
        return view('livewire.auto-logout');
    }
}

Blade file:

<div
    x-data="{
        timer: {{ $timer }}, // Initialize the timer in Alpine.js
        showModal: false,
        modalActive: false,
        init() {
            this.resetActivityTimer = () => {
                if (!this.modalActive) {
                    this.timer = {{ $timer }}; // Reset the timer locally
                    $wire.call('resetTimer'); // Call Livewire to reset the backend timer
                }
            };

            const activityEvents = ['mousemove', 'keydown', 'click'];
            activityEvents.forEach(e => document.addEventListener(e, this.resetActivityTimer));

            this.timerInterval = setInterval(() => {
                if (this.timer > 0) {
                    this.timer--;
                }
                if (this.timer <= 560 && this.timer > 0) {
                    this.showModal = true;
                    this.modalActive = true;
                } else if (this.timer <= 0) {
                    $wire.logout();
                }
            }, 1000);
        },
        stayLoggedIn() {
            this.timer = {{ $timer }}; // Reset timer locally
            $wire.call('resetTimer'); // Reset backend timer
            this.showModal = false;
            this.modalActive = false;
        },
        logoutNow() {
            $wire.logout();
        },
        cleanup() {
            clearInterval(this.timerInterval);
            const activityEvents = ['mousemove', 'keydown', 'click'];
            activityEvents.forEach(e => document.removeEventListener(e, this.resetActivityTimer));
        }
    }"
    x-init="init()"
    x-on:beforeunload.window="cleanup()"
>
    <!-- Modal content -->
    <div x-cloak x-show="showModal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center px-4">
        <div class="bg-white rounded-lg p-4 max-w-sm w-full">
            <h2 class="text-xl font-bold mb-4">Inactivity Warning</h2>
            <p>You will be logged out in <span x-text="Math.floor(timer / 60) + ':' + (timer % 60).toLocaleString('en-US', {minimumIntegerDigits: 2})"></span> due to inactivity.</p>
            <div class="mt-4 flex justify-between">
                <button @click="stayLoggedIn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                    Stay Logged In
                </button>
                <button @click="logoutNow" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
                    Log Out Now
                </button>
            </div>
        </div>
    </div>
</div>
0 likes
4 replies
LaryAI's avatar
Level 58

It seems like you're experiencing an issue with your Alpine.js timer where it's decrementing by 2 seconds instead of 1. Without seeing the actual code, I can only provide a general solution. Here's a basic example of how you might set up a countdown timer with Alpine.js that decrements by 1 second at a time:

<div x-data="timer()" x-init="start()">
    <span x-text="timeRemaining"></span>
    <div x-show="timeRemaining <= 560"> <!-- Show modal when time is less than or equal to 560 -->
        <!-- Modal content here -->
    </div>
</div>

<script>
function timer() {
    return {
        timeRemaining: 600,
        start() {
            const interval = setInterval(() => {
                if (this.timeRemaining > 0) {
                    this.timeRemaining--;
                } else {
                    clearInterval(interval);
                    // Trigger the logout or show the modal
                }
            }, 1000);
        }
    };
}
</script>

Make sure you only have one interval running at a time. If you accidentally set up multiple intervals, they could each decrement the timer, resulting in it counting down by 2s, 3s, etc., depending on how many intervals are running.

If you're using Livewire to manage the state, you might be sending an update to the Alpine component more than once, or there might be a conflict between Livewire's reactivity and Alpine's reactivity. Ensure that you're not initializing the Alpine component multiple times, which could cause multiple intervals to be created.

If you're still having trouble, please provide the actual Livewire and Alpine.js code you're using, and I can give you a more specific solution.

RayC's avatar
RayC
OP
Best Answer
Level 10

I figured out the problem. Apparently adding x-init="init()" was not needed, one I removed that portion of the div block it all worked as expected.

Updated blade file:

<div
    x-data="{
        timer: {{ $timer }}, // Initialize the timer in Alpine.js
        showModal: false,
        modalActive: false,
        init() {
            this.resetActivityTimer = () => {
                if (!this.modalActive) {
                    this.timer = {{ $timer }}; // Reset the timer locally
                    $wire.call('resetTimer'); // Call Livewire to reset the backend timer
                }
            };

            const activityEvents = ['mousemove', 'keydown', 'click'];
            activityEvents.forEach(e => document.addEventListener(e, this.resetActivityTimer));

            this.timerInterval = setInterval(() => {
                if (this.timer > 0) {
                    this.timer--;
                }
                if (this.timer <= 120 && this.timer > 0) {
                    this.showModal = true;
                    this.modalActive = true;
                } else if (this.timer <= 0) {
                    $wire.logout();
                }
            }, 1000);
        },
        stayLoggedIn() {
            this.timer = {{ $timer }}; // Reset timer locally
            $wire.call('resetTimer'); // Reset backend timer
            this.showModal = false;
            this.modalActive = false;
        },
        logoutNow() {
            $wire.logout();
        },
        cleanup() {
            clearInterval(this.timerInterval);
            const activityEvents = ['mousemove', 'keydown', 'click'];
            activityEvents.forEach(e => document.removeEventListener(e, this.resetActivityTimer));
        }
    }"
    x-on:beforeunload.window="cleanup()"
>
    <!-- Modal content -->
    <div x-cloak x-show="showModal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center px-4">
        <div class="bg-white rounded-lg p-4 max-w-sm w-full">
            <h2 class="text-xl font-bold mb-4">Inactivity Warning</h2>
            <p>You will be logged out in <span x-text="Math.floor(timer / 60) + ':' + (timer % 60).toLocaleString('en-US', {minimumIntegerDigits: 2})"></span> due to inactivity.</p>
            <div class="mt-4 flex justify-between">
                <button @click="stayLoggedIn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                    Stay Logged In
                </button>
                <button @click="logoutNow" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
                    Log Out Now
                </button>
            </div>
        </div>
    </div>
</div>
Snapey's avatar

@RayC make sure you check the network traffic.

Im not sure why you need to use livewire at all but it seems to me that you will be sending livewire events on every mouse movement, and the fire 100s of events at a time when you move the mouse over even a short distance,

Im pretty sure that the whole thing can be done in alpine

RayC's avatar
Level 10

@Snapey I can make it use Alpine only. The reason it is using livewire is because the timer limit will be set via a config file. I will probably convert it to a blade view when I refactor it. This was getting it running and working, I plan to refactor it now that I have it not counting down by 2.

Modified to use alpine for timer and not send a ton of Livewire event:

<div
    x-data="{
        timer: 600,
        showModal: false,
        modalActive: false,
        init() {
            this.resetActivityTimer = () => {
                if (!this.modalActive) {
                    this.timer = 600; // Reset the timer locally
                }
            };

            const activityEvents = ['mousemove', 'keydown', 'click'];
            activityEvents.forEach(e => document.addEventListener(e, this.resetActivityTimer));

            this.timerInterval = setInterval(() => {
                if (this.timer > 0) {
                    this.timer--;
                }
                if (this.timer <= 120 && this.timer > 0) {
                    this.showModal = true;
                    this.modalActive = true;
                } else if (this.timer <= 0) {
                    this.logoutNow();
                }
            }, 1000);
        },
        stayLoggedIn() {
            this.timer = 600;
            this.showModal = false;
            this.modalActive = false;
        },
        logoutNow() {
            // Directly call Livewire method to handle logout
            @this.logout()
        },
        cleanup() {
            clearInterval(this.timerInterval);
            const activityEvents = ['mousemove', 'keydown', 'click'];
            activityEvents.forEach(e => document.removeEventListener(e, this.resetActivityTimer));
        }
    }"
    x-on:beforeunload.window="cleanup()"
>
    <!-- Modal content -->
    <div x-cloak x-show="showModal" class="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center px-4">
        <div class="bg-white rounded-lg p-4 max-w-sm w-full">
            <h2 class="text-xl font-bold mb-4">Inactivity Warning</h2>
            <p>You will be logged out in <span x-text="Math.floor(timer / 60) + ':' + (timer % 60).toLocaleString('en-US', {minimumIntegerDigits: 2})"></span> due to inactivity.</p>
            <div class="mt-4 flex justify-between">
                <button @click="stayLoggedIn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                    Stay Logged In
                </button>
                <button @click="logoutNow" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
                    Log Out Now
                </button>
            </div>
        </div>
    </div>
</div>

Please or to participate in this conversation.