
If you are using Laravel Breeze Inertia starter kit in your project you might come to situation where you need to integrate laravel pagination through inertia frontend. In this snippet we will show how to create custom pagination component in Inertia.
I will suppose that we already have a laravel project setup along with Laravel Breeze starter kit, so i will not go into the details of setting up the project from scratch.
For the sake of this tutorial i assume that you are using Laravel breeze with Inertia and Reactjs stack. However you can apply this also to Inertia Vue.
Let’s create a migration for posts table which we will apply the pagination for. Create new model and migration using this command:
php artisan make:model Post -m
This command creates a Post model and the related migration file.
Open the new migration file and populate with this code:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('content'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('posts'); } };
Next open the app/Models/Post.php model and add the $fillable property like so:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Post extends Model { use HasFactory; protected $fillable = ["title", "content"]; }
Let’s fill our table with some dummy data, so there are many ways to do such thing. I will use a factory to insert dummy data using faker.
In the terminal create new factory:
php artisan make:factory PostFactory
Open database/factories/PostFactory.php and update with this code:
<?php namespace Database\Factories; use Illuminate\Database\Eloquent\Factories\Factory; class PostFactory extends Factory { public function definition(): array { return [ 'title' => fake()->word(), 'content' => fake()->paragraph() ]; } }
Now apply the new factory in the DatabaseSeeder.php like so:
DatabaseSeeder.php
<?php namespace Database\Seeders; use App\Models\Post; use App\Models\User; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. */ public function run(): void { Post::factory()->count(40)->create(); } }
Run the db:seed command:
php artisan db:seed
After running this command check the database table you will see that you have 40 records inserted.
The next step let’s create new inertia page to display the posts in a grid. Create new controller for posts:
php artisan make:controller PostsController
<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\Request; use Inertia\Inertia; class PostsController extends Controller { public function index() { return Inertia::render("Posts", [ "posts" => Post::paginate(10) ]); } }
In this code i added one action index to render a Posts page and we sent posts prop. We will receive this prop in the React component page.
Add a route for this page in routes/web.php inside of route middleware “auth“:
Route::middleware('auth')->group(function () { Route::get('/posts', [\App\Http\Controllers\PostsController::class, 'index']); });
Then create a new react page as a jsx component in resources/js/Pages/ named Posts:
resources/js/Pages/Posts.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import {Head} from "@inertiajs/react"; import Pagination from "@/Components/Pagination.jsx"; export default function Posts({ auth, posts }) { return ( <AuthenticatedLayout user={auth.user} header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Posts</h2>} > <Head title="Posts" /> <div className="py-12"> <table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400"> <thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> <th className="px-6 py-3">#</th> <th className="px-6 py-3">Title</th> <th className="px-6 py-3">Content</th> <th className="px-6 py-3">Actions</th> </tr> </thead> <tbody> { posts.data.map(post => { return ( <tr className="bg-white border-b dark:bg-gray-800 dark:border-gray-700" key={post.id}> <td className="px-6 py-4"> {post.id} </td> <td className="px-6 py-4"> {post.title} </td> <td className="px-6 py-4"> {post.content} </td> <td className="px-6 py-4"> </td> </tr> ) }) } </tbody> </table> <div className="mt-4 text-center"> <Pagination page={posts.current_page} path={posts.path} per_page={posts.per_page} total={posts.total} /> </div> </div> </AuthenticatedLayout> ) };
In this code we are receiving the props (auth, posts). You might wonder where is <AuthenticatedLayout /> component come from. These component already generated when you prepare your project for the first time with Laravel Breeze.
Then i added a table to render the posts. The table styled using tailwindcss classes. To display the posts in react is a matter of using the map() function over the posts.data collection.
At the bottom of the table i added the <Pagination /> component which we will see below.
Create resources/js/Components/Pagination.jsx
import {Link} from "@inertiajs/react"; import {useMemo} from "react"; export default function Pagination({page, path, per_page, total}) { const numberPages = useMemo(() => { return Math.ceil(total / per_page); }, [page]); const cssClass = (i) => { if(i === page) { return 'flex items-center justify-center px-3 h-8 text-blue-600 border border-gray-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white'; } return 'flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white'; } return numberPages > 1 ? ( <nav aria-label="Page navigation"> <ul className="inline-flex -space-x-px text-sm"> { page > 1 && ( <li> <Link href={`${path}?page=${page-1}`} preserveScroll className="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">« Previous</Link> </li> ) } { [...Array(numberPages)].map((e, i) => { return ( <li key={i+1}> <Link href={`${path}?page=${i+1}`} className={cssClass(i+1)} preserveScroll>{i+1}</Link> </li> ) }) } { page < numberPages && ( <li> <Link href={`${path}?page=${page+1}`} preserveScroll className="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Next »</Link> </li> ) } </ul> </nav> ) : null }
The <Pagination /> component accepts some props like so:
- page: The current page comes from laravel collection.
- path: The url of the collection
- per_page: How many items displayed per page.
- total: The total number of items.
These props passed whenever we use the component on any page that requires pagination like in the Posts.jsx page:
<Pagination page={posts.current_page} path={posts.path} per_page={posts.per_page} total={posts.total} />
In the Pagination component the numberPages variable calculates and returns the number of pages using React useMemo function. The cssClass() function return the css class of the link depending on whether it’s active or not.
In the return block we check for numberPages and it should be greater than 1. Then we display first the previous link, the numbers and the next link.