livewire: When using a custom pagination view, unable to go to the previous page.

Describe the bug While using a custom pagination view, using wire:click"previousPage" doesn’t trigger an action.

To Reproduce

// Livewire/User/History.php

namespace App\Http\Livewire\User;

use Auth;
use Livewire\Component;
use Livewire\WithPagination;

class History extends Component
{
    use WithPagination;

    public function render()
    {
        $actions = Auth::user()->actions()
                            ->orderBy('created_at', 'desc')
                            ->paginate(2);
        
        return view('livewire.user.history', [
            'actions' => $actions,
        ]);
    }
}
// livewire/user/history.blade.php

<div class="flex flex-col">
    <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
        <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
            <table class="min-w-full">
                <thead>
                    <tr>
                        <th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                            Action
                        </th>
                        <th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                            IP address
                        </th>
                        <th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                            Time
                        </th>
                    </tr>
                </thead>

                <tbody>
                    @if($actions)
                        @foreach($actions as $action)
                        <tr class="bg-white hover:bg-gray-50">
                            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
                                {{ $action->log_name }}.{{ $action->description }}
                            </td>
                            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
                                {{ $action->ip_address }}
                            </td>
                            <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
                                {{ \Carbon\Carbon::parse( $action->created_at )->DiffForHumans() }}
                            </td>
                        </tr>
                        @endforeach
                    @endif
                </tbody>
            </table>

            {{ $actions->links('livewire.table-pagination') }}
        </div>
    </div>
</div>
// livewire/table-pagination.blade.php

@if ($paginator->hasPages())
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
    <div class="hidden sm:block">
        <p class="text-sm leading-5 text-gray-700">
            Showing
            <span class="font-medium">{{ $paginator->firstItem() }}</span>
            to
            <span class="font-medium">{{ $paginator->lastItem() }}</span>
            of
            <span class="font-medium">{{ $paginator->total() }}</span>
            results
        </p>
    </div>

    <div class="flex-1 flex justify-between sm:justify-end">
        <span class="relative z-0 inline-flex shadow-sm">
            @if ($paginator->onFirstPage())
                <button type="button" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-300 transition ease-in-out duration-150 cursor-not-allowed" disabled>
                    <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
                        <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
                    </svg>
                </button>
            @else
                <button type="button" wire:click="previousPage" rel="prev" aria-label="@lang('pagination.previous')" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:outline-none active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150">
                    <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
                        <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
                    </svg>
                </button>
            @endif

            @if ($paginator->hasMorePages())
                <button type="button" wire:click="nextPage" rel="next" aria-label="@lang('pagination.next')" class="-ml-px relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-500 hover:text-gray-400 focus:z-10 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150">
                    <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
                        <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
                    </svg>
                </button>
            @else
                <button type="button" class="-ml-px relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-200 transition ease-in-out duration-150 cursor-not-allowed" disabled>
                    <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
                        <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
                    </svg>
                </button>
            @endif
        </span>
    </div>
</div>
@endif

Expected behavior On clicking the element with an attribute of wire:click=“previousPage” Livewire should supply the previous page.

Screenshots n/a

Desktop (please complete the following information):

  • Chrome (latest 81.0.x)
  • Firefox (latest 75.0.x)

Additional context

  • Using spatie’s laravel actions package for this example.
  • Livewire styles are in the html head and scripts in the body (at the end) using Laravel 7 tag syntax, loaded in the parent view using blade’s push and stack.
// .../views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="ltr">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- Shortcut icon -->
        <link rel="shortcut icon" href="">

        <!-- Site title and description -->
        <title>{{ is_string(config('app.page.name')) ? config('app.page.name') : config('app.page.name')[0] }} | {{ config('app.shortname') }}</title>
        <meta name="description" content="{{ config('app.description') }}">

        <!-- Stylesheets -->
        <link rel="stylesheet" href="{{ mix('css/fonts.css') }}">
        <link rel="stylesheet" href="{{ mix('css/app.css') }}">
        @stack('styles')

        <!-- CSRF Token -->
        <meta name="csrf-token" content="{{ csrf_token() }}">
    </head>
    <body class="antialiased bg-gray-100">
        @yield('content')

        <!-- Scripts -->
        <script src="{{ mix('js/manifest.js') }}"></script>
        <script src="{{ mix('js/vendor.js') }}"></script>
        <script src="{{ mix('js/app.js') }}"></script>
        @stack('scripts')
    </body>
</html>
// .../views/user/history.blade.php

@extends('layouts.app')

@push('styles')
    <livewire:styles>
@endpush

@section('content')
<div class="">
    @include('layouts.partials._header-nav')

    <header class="bg-white shadow-sm">
        <div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
            <h1 class="text-lg leading-6 font-semibold text-gray-900">
                History
            </h1>
        </div>
    </header>

    <main>
        <div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
            <div class="grid grid-cols-4 col-gap-8 row-gap-4">
                <div class="col-span-4 md:col-span-1">
                    @include('user.partials._account-nav')
                </div>

                <div class="col-span-4 md:col-span-3">
                    <livewire:user.history>
                </div>
            </div>
        </div>
    </main>
</div>
@endsection

@push('scripts')
    <livewire:scripts>
@endpush

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 6
  • Comments: 16 (3 by maintainers)

Commits related to this issue

Most upvoted comments

The problem appears to be that the “previous” button is being rendered without a wire:click directive when inactive, and then with a wire:click directive when active, as follows

    @if ($paginator->onFirstPage())
        <button disabled>Previous</button>
    @else 
        <button wire:click="previous">Previous</button>
    @endif

The result is that Livewire updates the attributes on the button, but doesn’t add a click handler for the wire:click directive. A similar problem can occur for the next page button if the wire:click directive is removed.

This suggests two work-arounds:

  1. Add the wire:click directive to both the disabled and active versions of the button - the disable attribute will prevent the click event from triggering the wire:click action.
  2. Add an id or wire:id such that the two versions have different ids (or add an id to just one or the other version). Then Livewire will see the buttons as different elements and completely replace one for the other, and in the process add the click handler for the wire:click directive.

I was able to fix this issue by adding ids to all of the elements in my pagination view.

For what it’s worth. I had this issue and I also added Id’s to all items in the paginator that were repetitive, making it difficult for Livewire to diff and the problem was solved. I used a similar view as @alexjustesen and I also had to add unique id’s to this section:

<p class="text-sm leading-5 text-gray-700">
            Showing
            <span class="font-medium">{{ $paginator->firstItem() }}</span>
            to
            <span class="font-medium">{{ $paginator->lastItem() }}</span>
            of
            <span class="font-medium">{{ $paginator->total() }}</span>
            results
</p>

Closing this because it’s been open too long - re-submit new issue to re-raise

The problem appears to be that the “previous” button is being rendered without a wire:click directive when inactive, and then with a wire:click directive when active, as follows

    @if ($paginator->onFirstPage())
        <button disabled>Previous</button>
    @else 
        <button wire:click="previous">Previous</button>
    @endif

The result is that Livewire updates the attributes on the button, but doesn’t add a click handler for the wire:click directive. A similar problem can occur for the next page button if the wire:click directive is removed.

This suggests two work-arounds:

  1. Add the wire:click directive to both the disabled and active versions of the button - the disable attribute will prevent the click event from triggering the wire:click action.
  2. Add an id or wire:id such that the two versions have different ids (or add an id to just one or the other version). Then Livewire will see the buttons as different elements and completely replace one for the other, and in the process add the click handler for the wire:click directive.

Perfect explanation!