@toniel/laravel-tanstack-pagination
v0.1.2
Published
Vue 3 composables for Laravel pagination with TanStack Query
Downloads
171
Maintainers
Readme
Laravel TanStack Pagination
Vue 3 composables untuk Laravel pagination dengan TanStack Query - Headless & Framework Agnostic.
💡 Looking for ready-to-use UI components? Check out
@toniel/laravel-tanstack-datatable- Pre-built DataTable components with this library.
📦 Installation
npm install @toniel/laravel-tanstack-pagination
# or
yarn add @toniel/laravel-tanstack-pagination
# or
pnpm add @toniel/laravel-tanstack-pagination
# or
bun add @toniel/laravel-tanstack-paginationPeer Dependencies
npm install vue @tanstack/vue-query🚀 Quick Start
import { usePagination } from '@toniel/laravel-tanstack-pagination'
import axios from 'axios'
const {
tableData,
pagination,
search,
currentPerPage,
sortBy,
sortDirection,
isLoading,
error,
handlePageChange,
handlePerPageChange,
handleSearchChange,
handleSortChange,
} = usePagination(
(filters) => axios.get('/api/users', { params: filters }),
{
queryKey: 'users',
defaultPerPage: 10
}
)✨ Features
- ✅ Headless - Bring your own UI components
- ✅ Laravel pagination support out of the box
- ✅ TanStack Query integration for caching & refetching
- ✅ Search with automatic debounce
- ✅ Sorting (server-side)
- ✅ Filtering with flexible API
- ✅ Table numbering helper for sequential row numbers
- ✅ TypeScript fully typed
- ✅ Framework agnostic - Works with any UI library
📖 API Reference
usePagination(fetchFn, options)
Main composable for handling Laravel pagination.
Parameters:
fetchFn:(filters: PaginationFilters) => Promise<LaravelPaginationResponse<T>>- Function that fetches data from Laravel API
options:UsePaginationOptions<T>queryKey:string- Unique key for the query (required)defaultPerPage:number- Items per page (default:10)defaultSearch:string- Default search query (default:'')defaultSort:SortState- Default sort state (default:null)- ...all other TanStack Query options
Returns:
| Property | Type | Description |
|----------|------|-------------|
| tableData | Ref<T[]> | Computed array of data items |
| pagination | Ref<LaravelPaginationResponse \| null> | Full Laravel pagination response |
| currentPage | Ref<number> | Current page number |
| perPage | Ref<number> | Items per page |
| search | Ref<string> | Current search query |
| sortBy | Ref<string \| null> | Current sort column |
| sortDirection | Ref<'asc' \| 'desc'> | Sort direction |
| filters | ComputedRef<PaginationFilters> | All active filters (including custom filters) |
| customFilters | Ref<Record<string, any>> | Custom filter state (for external use) |
| isLoading | Ref<boolean> | Loading state from TanStack Query |
| error | Ref<Error \| null> | Error state from TanStack Query |
| handlePageChange | (page: number) => void | Change page handler |
| handlePerPageChange | (perPage: number) => void | Change per page handler |
| handleSearchChange | (search: string) => void | Search change handler (debounced) |
| handleSortChange | (column: string) => void | Toggle sort on column |
| handleFilterChange | (filters: Record<string, any>) => void | Bulk update custom filters |
| setFilter | (key: string, value: any) => void | Add custom filter |
| removeFilter | (key: string) => void | Remove custom filter |
| resetFilters | () => void | Reset all filters to defaults |
| getNumberingColumn | (options?) => ColumnDef | Get numbering column for TanStack Table |
| refetch | TanStack Query refetch function |
| ...queryResult | All other TanStack Query return values |
useTableNumbering()
Helper composable for creating table row numbering columns.
Returns:
createNumberingColumn(pagination, perPage, options?): Function to create a numbering column definition for TanStack Table
Example:
const { createNumberingColumn } = useTableNumbering()
const numberingColumn = createNumberingColumn(
pagination, // ref from usePagination
perPage, // ref from usePagination
{
header: '#',
enableSorting: false
}
)🔗 TypeScript Types
// Laravel Pagination Response
type LaravelPaginationResponse<T = any> = {
data: T[]
links: {
first: string | null
last: string | null
next: string | null
prev: string | null
}[]
meta: {
current_page: number
from: number
last_page: number
links: Array<{
url: string | null
label: string
active: boolean
}>
path: string
per_page: number
to: number
total: number
}
}
// Pagination Filters
interface PaginationFilters {
page?: number
per_page?: number
search?: string
[key: string]: any // Custom filters
}
// Sort State
interface SortState {
column: string | null
direction: 'asc' | 'desc'
}🎯 Usage with UI Libraries
With Custom Table Component
<template>
<div>
<!-- Search -->
<input v-model="search" @input="handleSearchChange" />
<!-- Table -->
<table>
<thead>
<tr>
<th @click="handleSortChange('name')">Name</th>
<th @click="handleSortChange('email')">Email</th>
</tr>
</thead>
<tbody>
<tr v-for="user in tableData" :key="user.id">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
</tbody>
</table>
<!-- Pagination -->
<button
:disabled="pagination?.meta.current_page === 1"
@click="handlePageChange(pagination.meta.current_page - 1)"
>
Previous
</button>
<span>Page {{ pagination?.meta.current_page }} of {{ pagination?.meta.last_page }}</span>
<button
:disabled="pagination?.meta.current_page === pagination?.meta.last_page"
@click="handlePageChange(pagination.meta.current_page + 1)"
>
Next
</button>
</div>
</template>With TanStack Table
<script setup lang="ts">
import { usePagination } from '@toniel/laravel-tanstack-pagination'
import { createColumnHelper, FlexRender, getCoreRowModel, useVueTable } from '@tanstack/vue-table'
const columnHelper = createColumnHelper<User>()
const {
tableData,
pagination,
perPage,
getNumberingColumn,
// ... other returns
} = usePagination(fetchUsers, { queryKey: 'users' })
const columns = [
getNumberingColumn({ header: '#' }),
columnHelper.accessor('name', { header: 'Name' }),
columnHelper.accessor('email', { header: 'Email' }),
]
const table = useVueTable({
get data() { return tableData.value },
columns,
getCoreRowModel: getCoreRowModel(),
})
</script>With Shadcn-vue or Radix-vue
For ready-to-use DataTable component with shadcn-vue styling, use:
npm install @toniel/laravel-tanstack-datatableSee: https://www.npmjs.com/package/@toniel/laravel-tanstack-datatable
🔍 Filtering
The usePagination composable provides comprehensive filter management with proper state persistence across page changes.
Single Filter Operations
const {
setFilter,
removeFilter,
customFilters,
refetch,
} = usePagination(fetchFn, { queryKey: 'users' })
// Set a single filter
setFilter('status', 'active')
setFilter('category', 'A')
// Remove a filter
removeFilter('status')
// The query will automatically refetch when filters changeBulk Filter Operations
const {
handleFilterChange,
customFilters,
refetch,
} = usePagination(fetchFn, { queryKey: 'users' })
// Bulk update multiple filters at once
handleFilterChange({
status: 'active',
category: 'A',
year: '2024'
})
// Or pass partial updates (will merge with existing)
handleFilterChange({
...customFilters.value,
status: 'inactive'
})Filter State Persistence
Custom filters are properly included in the TanStack Query cache key, ensuring:
- Filters persist when changing pages
- Filters persist when changing per page
- Correct cache invalidation on filter changes
- Back/forward navigation works correctly
// Filters are automatically included in queryKey
const { filters } = usePagination(fetchFn, { queryKey: 'users' })
// filters.value = { page: 1, per_page: 10, search: '', status: 'active', category: 'A' }Reset Filters
const {
resetFilters,
customFilters,
search,
perPage,
} = usePagination(fetchFn, { queryKey: 'users', defaultPerPage: 20 })
// Reset all filters to defaults
resetFilters()
// After reset:
// - customFilters = {}
// - search = ''
// - perPage = 20
// - page = 1Example: Complex Filter Form
<script setup lang="ts">
import { ref, computed } from 'vue'
import { usePagination } from '@toniel/laravel-tanstack-pagination'
// Filter form state (separate from composable)
const filterForm = ref({
status: '',
category: '',
year: new Date().getFullYear().toString(),
month: '',
})
const {
tableData,
pagination,
customFilters,
setFilter,
removeFilter,
handlePageChange,
refetch,
} = usePagination(
(filters) => axios.get('/api/transactions', { params: filters }),
{ queryKey: 'transactions', defaultPerPage: 20 }
)
// Apply filters
const applyFilters = () => {
// Clear existing custom filters first
if (filterForm.value.status) {
setFilter('status', filterForm.value.status)
} else {
removeFilter('status')
}
if (filterForm.value.category) {
setFilter('category', filterForm.value.category)
} else {
removeFilter('category')
}
if (filterForm.value.year) {
setFilter('year', filterForm.value.year)
} else {
removeFilter('year')
}
if (filterForm.value.month) {
setFilter('month', filterForm.value.month)
} else {
removeFilter('month')
}
refetch()
}
// Clear all filters
const clearFilters = () => {
filterForm.value = { status: '', category: '', year: new Date().getFullYear().toString(), month: '' }
removeFilter('status')
removeFilter('category')
removeFilter('year')
removeFilter('month')
refetch()
}
</script>🔧 Laravel Backend Setup
Your Laravel API should return paginated responses in this format:
// UserController.php
public function index(Request $request)
{
$query = User::query();
// Search
if ($request->has('search')) {
$query->where('name', 'like', "%{$request->search}%")
->orWhere('email', 'like', "%{$request->search}%");
}
// Sorting (format: sort[column]=direction)
foreach ($request->all() as $key => $value) {
if (str_starts_with($key, 'sort[')) {
preg_match('/sort\[(.*?)\]/', $key, $matches);
$column = $matches[1] ?? null;
if ($column) {
$query->orderBy($column, $value);
}
}
}
// Paginate
$perPage = $request->input('per_page', 10);
return $query->paginate($perPage);
}The response will automatically be in the correct format:
{
"data": [...],
"links": {...},
"meta": {
"current_page": 1,
"from": 1,
"to": 10,
"total": 100,
"per_page": 10,
"last_page": 10
}
}🔗 Related Packages
@toniel/laravel-tanstack-datatable- Pre-built DataTable components@tanstack/vue-query- Data fetching & caching library@tanstack/vue-table- Headless table library
🌟 Show Your Support
If this package helped you, please consider:
- ⭐ Starring the GitHub repository
- 🐛 Reporting bugs
- 💡 Suggesting new features
- 🔧 Contributing code
📄 License
🤝 Contributing
Contributions, issues and feature requests are welcome!
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for more details.
📮 Contact
🙏 Acknowledgments
Built with these amazing libraries:
