Laravel Pagination with Inertia and React + Shadcn UI
For the past few months, I’ve been fully focused on developing my product, Lunch, and I found that there is no React component for Laravel Pagination. Since I am using Shadcn UI, I decided to create the component with a little help.
Backend Steps
Let’s start by making the query to return paginated results.
User::query()
->paginate(15)
Frontend Steps
I’m using TypeScript as well, so I’ve created a generic type for the paginated results, which you can add to your index.d.ts.
export interface PaginatedResponse<T> {
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;
}
Now, let’s modify the pagination component provided by Shadcn to work with Inertia.js. Here’s the updated Pagination.tsx file:
import { ButtonProps, buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Link } from '@inertiajs/react';
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
import * as React from 'react';
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
<nav
role="navigation"
aria-label="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
);
Pagination.displayName = 'Pagination';
const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<'ul'>>(
({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
)
);
PaginationContent.displayName = 'PaginationContent';
const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(
({ className, ...props }, ref) => (
<li
ref={ref}
className={cn('', className)}
{...props}
/>
)
);
PaginationItem.displayName = 'PaginationItem';
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, 'size'> &
Omit<React.ComponentProps<typeof Link>, 'size'>;
const PaginationLink = ({ className, isActive, size = 'icon', ...props }: PaginationLinkProps) => (
<Link
aria-current={isActive ? 'page' : undefined}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size
}),
className
)}
{...props}
/>
);
PaginationLink.displayName = 'PaginationLink';
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 pl-2.5', className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = 'PaginationPrevious';
const PaginationNext = ({ className, ...props }: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 pr-2.5', className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
PaginationNext.displayName = 'PaginationNext';
const PaginationEllipsis = ({ className, ...props }: React.ComponentProps<'span'>) => (
<span
aria-hidden
className={cn('flex h-9 w-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = 'PaginationEllipsis';
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious
};
Now you can use it anywhere with your table. Here’s an example:
const { users } = usePage().props as unknown as PageProps<{
users: PaginatedResponse<App.Models.User>;
}>;
return (
<>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[120px]">Name</TableHead>
<TableHead className="text-center">Email</TableHead>
<TableHead className="text-center">Role</TableHead>
<TableHead className="text-center">Created</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.data?.map((user) => {
return (
<TableRow key={user.id}>
<TableCell className="w-[100px]">
{user.name}
</TableCell>
<TableCell className="text-center">
{user.email}
</TableCell>
<TableCell className="text-center">
<Badge>{user.role}</Badge>
</TableCell>
<TableCell className="text-center">
<span className="text-sm text-muted-foreground">
{formatDistanceToNow(
new Date(user.created_at),
{
addSuffix: true
}
)}
</span>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
<InertiaPagination paginateItems={users} />
</>)
This should make your pagination work without any issues. Make sure you are using Shadcn UI, which comes with the pagination component. With some minor adjustments, if you’re planning to use it with Vue as well, it should work. Thanks for reading! I hope this guide helps you have a more efficient and enjoyable coding experience. Have a great day!