Skip to content

Instantly share code, notes, and snippets.

@itsanishjain
Created February 12, 2024 10:38
Show Gist options
  • Save itsanishjain/c52ab14823f8b7f1fc643406dc38df50 to your computer and use it in GitHub Desktop.
Save itsanishjain/c52ab14823f8b7f1fc643406dc38df50 to your computer and use it in GitHub Desktop.
Search with pagincation with Nextjs14
// utils.ts
export const generatePagination = (currentPage: number, totalPages: number) => {
// If the total number of pages is 7 or less,
// display all pages without any ellipsis.
if (totalPages <= 7) {
return Array.from({ length: totalPages }, (_, i) => i + 1);
}
// If the current page is among the first 3 pages,
// show the first 3, an ellipsis, and the last 2 pages.
if (currentPage <= 3) {
return [1, 2, 3, "...", totalPages - 1, totalPages];
}
// If the current page is among the last 3 pages,
// show the first 2, an ellipsis, and the last 3 pages.
if (currentPage >= totalPages - 2) {
return [1, 2, "...", totalPages - 2, totalPages - 1, totalPages];
}
// If the current page is somewhere in the middle,
// show the first page, an ellipsis, the current page and its neighbors,
// another ellipsis, and the last page.
return [
1,
"...",
currentPage - 1,
currentPage,
currentPage + 1,
"...",
totalPages,
];
};
// Search component
("use client");
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useDebouncedCallback } from "use-debounce";
function Search({ placeholder }: { placeholder: string }) {
const searchParams = useSearchParams();
const { replace } = useRouter();
const pathname = usePathname();
const handleSearch = useDebouncedCallback((term) => {
const params = new URLSearchParams(searchParams);
params.set("page", "1");
if (term) {
params.set("query", term);
} else {
params.delete("query");
}
replace(`${pathname}?${params.toString()}`);
}, 300);
return (
<div className="relative flex flex-1 flex-shrink-0">
<label htmlFor="search" className="sr-only">
Search
</label>
<input
className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
placeholder={placeholder}
onChange={(e) => {
handleSearch(e.target.value);
}}
defaultValue={searchParams.get("query")?.toString()}
/>
</div>
);
}
// Table Component
function MembersTable({
query,
currentPage,
}: {
query: string;
currentPage: number;
}) {
return <div></div>;
}
// Pagination
function Pagination({ totalPages }: { totalPages: number }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
const createPageURL = (pageNumber: number | string) => {
const params = new URLSearchParams(searchParams);
params.set("page", pageNumber.toString());
return `${pathname}?${params.toString()}`;
};
const allPages = generatePagination(currentPage, totalPages);
return (
// Some tailwind pagination Ui
<div>
{allPages.map((page, index) => {
return <div key={index}>{page}</div>;
})}
</div>
);
}
// Server Component
import { Suspense } from "react";
export default async function members({
searchParams,
}: {
searchParams?: {
query?: string;
page?: string;
};
}) {
const fetchTotalMembers = async ({ query }: { query: string }) => {
// Call Database to get members with matched query
return 5;
};
const query = searchParams?.query || "";
const currentPage = Number(searchParams?.page) || 1;
const totalPages = await fetchTotalMembers({ query });
return (
<div>
<div className="flex w-full items-center justify-between">
<h1 className="text-2xl">Members</h1>
</div>
<div className="mt-4 flex items-center justify-between gap-2 md:mt-8">
<Search placeholder="Search invoices..." />
{/* Show fetched members */}
<Suspense key={query + currentPage} fallback={"loading...."}>
<MembersTable currentPage={currentPage} query={query} />
</Suspense>
{/* Show pagination ui */}
<Pagination totalPages={totalPages} />
</div>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment