Back to Course |
Vue.js 3 + Laravel 11 + Vite: SPA CRUD

Category Dropdown: Filtering Data

Now that we have a select input for the category, let's make it work as a filter. After selecting a category, the list should show only the posts that only belong to that category.

selected category filter


Vue: Watch the Selected Category

To get all the posts that belong to a category, we need to watch the selectedCategory variable. First, we need to import watch from the Vue.

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

<template>
// ...
</template>
 
<script setup>
import { onMounted, ref } from "vue";
import { onMounted, ref, watch } from "vue";
import { TailwindPagination } from 'laravel-vue-pagination';
import usePosts from "@/composables/posts";
import useCategories from "@/composables/categories";
// ...
</script>

Now using this watch() function, we can watch the selectedCategory variable for the changes with two values: current and previous.

Inside watch we can call the getPosts which already accepts page, but we can now also pass the current value of the selected category.

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

<script setup>
import { onMounted, ref, watch } from "vue";
// ...
 
watch(selectedCategory, (current, previous) => {
getPosts(1, current)
})
</script>

Composable: Accept & Pass the Category

Next, we need to modify posts Composable so that getPosts would accept the category, which by default will be an empty string.

And the same as for the page parameter, pass the category to the API request.

resources/js/composables/posts.js:

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

Laravel: Filter by Category

And now, back to the back-end: we need to add a conditional clause to the Eloquent query where we get the posts.

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

use Illuminate\Database\Eloquent\Builder;
 
class PostController extends Controller
{
public function index()
{
$posts = Post::with('category')
->when(request('category'), function (Builder $query) {
$query->where('category_id', request('category'));
})
->paginate(10);
 
return PostResource::collection($posts);
}
}

Now the filter should work.


Fixing Pagination

But wait: the pagination is broken! For example, if you would change paginate to 2 records instead of 10 and select the category which has more posts than 2, you will see the pagination. But after clicking on the second page, the category wouldn't be passed and the pagination would break.

This can be seen clearly in the developer tools network tab.

broken pagination

Unfortunately, the fix isn't documented, but in one of the GitHub issues the author provided the solution. We need to pass the page as a parameter.

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">
// ...
 
<TailwindPagination :data="posts" @pagination-change-page="getPosts" class="mt-4" />
<TailwindPagination :data="posts" @pagination-change-page="page => getPosts(page, selectedCategory)" class="mt-4" />
</div>
</div>
</template>
 
<script setup>
// ...
</script>

Now the pagination with the filter works as expected.

working pagination with filter