Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here

Table Pagination with React "Helper"

Premium
6 min read

In this lesson, we will add pagination to our table. It won't be an easy one-minute process.


Modifying Controller for Pagination

app/Http/Controllers/TaskController.php

// ...
 
public function index()
{
return Inertia::render('Tasks/Index', [
'tasks' => Task::all()
'tasks' => Task::paginate(5)
]);
}
 
// ...

Now, our Task List page will stop working because of a TypeScript structure mismatch. We return paginate() from Controller, which is no longer the array of the Task type. Let's fix this.


Creating Paginated Response Type

We return tasks from the Controller not as a list but as a paginated object.

So, we need to create a specific new type for it, corresponding to the structure returned by Laravel pagination.

resources/js/types/index.d.ts

// ...
 
export interface PaginatedResponse<T = Task | null> {
current_page: number;
data: T[];
first_page_url: string;
from: number;
last_page: number;
last_page_url: string;
links: {
url: string | null;
label: string;
active: boolean;
}[];
next_page_url: string | null;
path: string;
per_page: number;
prev_page_url: string | null;
to: number;
total: number;
}

Accepting Paginated Response in React

In three places, we need to change the type of data we get.

  • We import our new PaginatedResponse type
  • We use it in the export function instead of...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (29 h 14 min)

You also get:

54 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

J
Joe ✓ Link copied!

The shadcn pagination component uses a regular <a> tag for the links, so you'll notice that the pagination links cause a full page refresh. This can be remedied by updating the shadcn component to use the Inertia <Link> component.

The great thing about shadcn and the way that the Laravel Starter Kits are implemented is that you can modify the components in this way, so no messy extending. Just be careful not to overwrite the changes later!

PK
Povilas Korop ✓ Link copied!

Thanks for the valuable comment, Joe, great notice!

MC
Mark Constable ✓ Link copied!

That's a great point Joe however I just tried to naively update resources/js/components/ui/pagination.tsx to use "Link" instead of "a" and got myself into all sorts of trouble. Anyone got some hints or a Gist?

J
Joe ✓ Link copied!

Yeah, it's pretty easy to break. I ended up enlisting the help of AI to do it. Here's what we came up with...

First import the inertia <Link> at the top:

import { Link } from "@inertiajs/react"

Then redefine the PaginationLinkProps type slightly:

type PaginationLinkProps = {
  isActive?: boolean
  href: string
  size?: "default" | "sm" | "lg" | "icon"
  className?: string
  children?: React.ReactNode
}

Finally replace the PaginationLink function with this:

function PaginationLink({
  className,
  isActive,
  size = "icon",
  href,
  children,
  ...props
}: PaginationLinkProps) {
  return (
    <Link
      href={href}
      aria-current={isActive ? "page" : undefined}
      className={cn(
        buttonVariants({
          variant: isActive ? "outline" : "ghost",
          size,
        }),
        className
      )}
      {...props}
    >
      {children}
    </Link>
  )
}
SP
Steve Popoola ✓ Link copied!

Just an observation, after the custom table-pagination.tsx component is created, the step where the component is used is missing. This can be confusing to readers who are just following along. The following needs to be added to the Tasks/Index.tsx for the pagination to show;

//Import first
import { TablePagination } from '@/components/table-pagination';

// Then use below
        <TablePagination resource={tasks} />
     </div>
</AppLayout>
PK
Povilas Korop ✓ Link copied!

Wow, how could I miss this... fixed now! Thanks Steve for being a proofreader! Need to spend more time on double-checking everything in the future. Perhaps too much in a hurry to release everything related to Laravel 12, when people are asking so many question.

SP
Steve Popoola ✓ Link copied!

No worries my friend, happy to help! Hope to see you at Laravel Live London :)

T
TuanTQ ✓ Link copied!
Each child in a list should have a unique "key" prop.

Check the render method of `TablePagination`. See https://react.dev/link/warning-keys for more information. Error Component Stack
    at TablePagination (table-pagination.tsx:5:35)
    at div (<anonymous>)
    at main (<anonymous>)
    at SidebarInset (sidebar.tsx:302:25)
    at AppContent (app-content.tsx:8:30)
    at div (<anonymous>)
    at TooltipProvider (tooltip.tsx:7:3)
    at SidebarProvider (sidebar.tsx:55:3)
    at AppShell (app-shell.tsx:9:28)
    at AppSidebarLayout (app-sidebar-layout.tsx:9:44)
    at default (app-layout.tsx:10:19)
    at Index (Index.tsx:17:5)

I get error in console log. And I fixed it in resources/js/lib/generate-pagination-links.tsx by adding 2 keys to PaginationEllipsis key="ellipsis-before" and key="ellipsis-after"

...
if (2 < currentPage && currentPage < totalPages - 1) {
            pages.push(<PaginationEllipsis key="ellipsis-before"/>);
            pages.push(
                <PaginationItem key={currentPage}>
                    <PaginationLink href="" isActive={true}>
                        {currentPage}
                    </PaginationLink>
                </PaginationItem>,
            );
        }
        pages.push(<PaginationEllipsis key="ellipsis-after"/>);
        for (let i = totalPages - 1; i <= totalPages; i++) {
            pages.push(
                <PaginationItem key={i}>
                    <PaginationLink href={path + pageQuery + i} isActive={i === currentPage}>
                        {i}
                    </PaginationLink>
                </PaginationItem>,
            );
        }
MS
Mike Scott ✓ Link copied!

For the PaginatedResponse interface to be generally useful you need to remove the default type Task and leave the generic declaration without any default type. It makes no sense to have an arbitrary dependency on Task. You can close the type declaration where it's used by specifying the type at that point.

For example, the table pagination component would become an open generic function: export function TablePagination<T>({resource}: {resource: PaginatedResponse<T>})...

You then close the function when it's used in the TSX markup, for example if you're paginating a list of Task, like this: <TablePagination<Task> resource={tasks} />

You also have to remove the dependency on Task from TablePagination and from generatePaginationLinks, making them also generic on T.

After doing that, you have pagination components that can be used in any project without having to edit the code and change the dependency on Task to something else.

M
Modestas ✓ Link copied!

Thank you for this! It's something we might have lacked knowledge on in typescript as I did not get it working correctly.

Will most likely update the article with your suggestion :)

K
kornelkornecki ✓ Link copied!

I think it would be most useful to add a chapter with data-table. https://ui.shadcn.com/docs/components/data-table

M
Modestas ✓ Link copied!

We will chat internally to see if we should add it, but it will require a lot more work as there's more to DataTables than what we have right now

K
kornelkornecki ✓ Link copied!

It surely is but that would be the real life case. If I may suggest something - it should be kind of drop-in replacement for datatables you have in the Quick Panel which I always was big fan of. Thank you for considering it anyway.

M
Modestas ✓ Link copied!

Yeah, so this is exactly what I had in mind when I said that it's complicated :)

The Quickadmin DataTables are complex and offer a lot of functionality, which is not easilly replicatable (thanks to a great package we used then!). To make an almost identical copy - it would take us a lot of time, and then to explain everything and how it works - even more. Maybe it's a good idea to start an open-source project, but for us at this moment it's a no-go, sorry!

ps. Happy to see a happy customer of product we had!

K
kornelkornecki ✓ Link copied!

Yes I quite understand. I really liked it and I learned a lot from it. I think your way of doing things and how you explain them is compatible with my mind. Thank you for all your great work.