Courses

Vue.js 3 + Laravel 11 API + Vite: SPA CRUD

Pagination with External Vue Package

Summary of this lesson:
- Installing laravel-vue-pagination package
- Implementing pagination in Laravel API
- Adding pagination UI with Tailwind styling
- Managing paginated data in Vue components

Loading the whole list of data into a table isn't logical, especially if you can hundreds or thousands of records. This is where the pagination comes in. In this lesson, we will add pagination to the posts table using the laravel-vue-pagination package.

finished vue pagination


First, of course, we need to install the package.

npm install laravel-vue-pagination

Next, instead of just calling all posts in the Controller, we need to paginate them.

app/Http/Controllers/Api/PostController.php:

class PostController extends Controller
{
public function index()
{
return PostResource::collection(Post::all());
return PostResource::collection(Post::paginate(10));
}
}

Next, in the PostsIndex Vue component, in the v-for, we need to change posts to posts.data instead.

resources/js/components/Posts/Index.vue:

<template>
// ...
<tr v-for="post in posts">
<tr v-for="post in posts.data">
// ...
</template>

Then our posts variable in the Composable needs to become an object instead of array.

resources/js/composables/posts.js:

import { ref } from 'vue'
 
export default function usePosts() {
const posts = ref([])
const posts = ref({})
 
const getPosts = async () => {
axios.get('/api/posts')
.then(response => {
posts.value = response.data.data;
})
}
 
return { posts, getPosts }
}

Next, we need to set a page parameter to the getPosts. Also, set that page as a parameter to the URL. And we don't need to return the data result. We will return the full API response.

resources/js/composables/posts.js:

import { ref } from 'vue'
 
export default function usePosts() {
const posts = ref({})
 
const getPosts = async () => {
axios.get('/api/posts')
const getPosts = async (page = 1) => {
axios.get('/api/posts?page=' + page)
.then(response => {
posts.value = response.data.data;
posts.value = response.data;
})
}
 
return { posts, getPosts }
}

Now we need to add pagination links below the table.

To do that, we also need to import the package in the PostsIndex Vue component.

Because we are using Tailwind for the design, we will import the Tailwind version, but if you need there is also a Bootstrap version. Or you may use a renderless pagination and style it yourself.

resources/js/components/Posts/Index.vue:

<template>
<div class="overflow-hidden overflow-x-auto p-6 bg-white border-gray-200">
<div class="min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-200 border">
// ...
</table>
 
<TailwindPagination :data="posts" @pagination-change-page="getPosts" class="mt-4" />
</div>
</div>
</template>
 
<script setup>
import { onMounted } from "vue";
import { TailwindPagination } from 'laravel-vue-pagination';
import usePosts from "@/composables/posts";
 
const { posts, getPosts } = usePosts()
onMounted(() => {
getPosts()
})
</script>

Now we have a working pagination.

vue pagination

Previous: Eloquent API Resources to Transform Fields
avatar

I followed the lesson steps but when i add the pagenation links the whole table disapear here is the code:

Index.vue

<template>
    <div class="overflow-hidden overflow-x-auto p-6 bg-white border-gray-200">
        <div class="min-w-full align-middle">
            <table class="min-w-full divide-y divide-gray-200 border">
                <thead>
                    <tr>
                        <th class="px-6 py-3 bg-gray-50 text-left">
                            <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">ID</span>
                        </th>
                        <th class="px-6 py-3 bg-gray-50 text-left">
                            <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Title</span>
                        </th>
                        <th class="px-6 py-3 bg-gray-50 text-left">
                            <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Content</span>
                        </th>
                        <th class="px-6 py-3 bg-gray-50 text-left">
                            <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Created at</span>
                        </th>
                    </tr>
                </thead>
                <tbody class="bg-white divide-y divide-gray-200 divide-solid">
                    <tr v-for="post in posts.data" :key="post.id"> 
                        <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                            {{ post.id }}
                        </td>
                        <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                            {{ post.title }}
                        </td>
                        <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                            {{ post.content }}
                        </td>
                        <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
                            {{ post.created_at }}
                        </td>
                    </tr> 
                    
                </tbody>
            </table>
            <TailwindPagination :data="posts" @pagination-change-page="getPosts" class="mt-4" /> 

        </div>
    </div>
</template>



<script setup>
import { onMounted } from 'vue';
import { TailwindPagination } from 'laravel-vue-pagination'; 
import usePosts from "@/composables/posts";

const { posts, getPosts } = usePosts()
onMounted(() => {
    getPosts()
})

</script>

posts.js

import axios from 'axios'
import { ref } from 'vue'

export default function usePosts() {
    const posts = ref({}) 

    const getPosts = async (page = 1) => { 
        axios.get('/api/posts?page=' + page) 
            .then(response => {
                posts.value = response.data; 
            })
    }

    return { posts, getPosts }
}
avatar
Uncaught TypeError: Cannot read properties of undefined (reading 'computed')
    at laravel-vue-pagination.es.js:420:9
    at normalizeChildren (runtime-core.esm-bundler.js:7049:34)
    at createBaseVNode (runtime-core.esm-bundler.js:6860:5)
    at _createVNode (runtime-core.esm-bundler.js:6937:10)
    at createVNodeWithArgsTransform (runtime-core.esm-bundler.js:6814:10)
    at createBlock (runtime-core.esm-bundler.js:6787:5)
    at Proxy.ee (laravel-vue-pagination.es.js:413:15)
    at renderComponentRoot (runtime-core.esm-bundler.js:896:44)
    at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5575:57)
    at ReactiveEffect.run (reactivity.esm-bundler.js:185:25)
		```
avatar

It seems there a problem with a package. See this issue. Sorry, nothing can do to help here, only that try different Vue versions.

avatar
You can use Markdown
avatar
You can use Markdown