Backend Development

Laravel Breeze Inertia Creating Custom Pagination Component

Laravel Breeze Inertia Creating Custom Pagination Component

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">&laquo; 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 &raquo;</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.

 

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments