Skip to content

Instantly share code, notes, and snippets.

@smakosh
Created March 16, 2024 22:06
Show Gist options
  • Save smakosh/84d90a91a4e89f746c04ea58abbebe67 to your computer and use it in GitHub Desktop.
Save smakosh/84d90a91a4e89f746c04ea58abbebe67 to your computer and use it in GitHub Desktop.
Next.js Offset/limit pagination
export const LIMIT = 10;
export const RANGE = 5;
import { Launch } from "@/types/launch";
import { LIMIT } from "@/app/_constants";
import { HomeProps } from "@/app/_types/params";
export const getLaunches = async (
offset: HomeProps["searchParams"]["offset"]
) => {
const response = await fetch("https://api.spacexdata.com/v5/launches/query", {
method: "POST",
body: JSON.stringify({
limit: LIMIT,
offset: offset ? Number(offset) * 10 : 20,
}),
});
const data = await response.json();
return {
launches: data.docs as Launch[],
currentPage: data.page,
total: data.totalPages,
};
};
import { unstable_noStore as noStore } from "next/cache";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { HomeProps } from "@/app/_types/params";
import { getLaunches } from "@/app/_services/getLaunches";
import { RANGE } from "@/app/_constants";
const Launches = async ({ offset }: HomeProps["searchParams"]) => {
noStore();
const { launches, currentPage, total } = await getLaunches(offset);
function* paginationGen() {
let i = 1;
let left = Math.max(1, currentPage - RANGE);
let right = Math.min(total, currentPage + RANGE);
// Is the current index displayable
const shouldDisplay = (at: number) => {
return at === 1 || at === total || (at >= left && at <= right);
};
// The next index I should jump to based on my current position
const next = (at: number) => {
if (at == 1 || at == total) return at + 1;
if (at < left) return left;
if (at > right) return total;
return at + 1;
};
while (i <= total) {
if (shouldDisplay(i)) {
yield i;
} else {
yield "...";
}
i = next(i);
}
}
return (
<>
<ul>
{launches.map((launch) => (
<li key={launch.id}>{launch.name}</li>
))}
</ul>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href={`/?offset=${currentPage - 1}`}
aria-disabled={currentPage === 1}
/>
</PaginationItem>
{Array.from(paginationGen()).map((item, i) => {
if (item === "...") {
return (
<PaginationItem key={i}>
<PaginationEllipsis />
</PaginationItem>
);
}
return (
<PaginationItem key={`paginate-${i}`}>
<PaginationLink
href={`/?offset=${item}`}
aria-disabled={item === currentPage}
>
{item}
</PaginationLink>
</PaginationItem>
);
})}
<PaginationItem>
<PaginationNext
href={`/?offset=${currentPage + 1}`}
aria-disabled={currentPage === total}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</>
);
};
export default Launches;
import { Suspense } from "react";
import Launches from "./_components/launches";
import { HomeProps } from "./_types/params";
export default async function Home({ searchParams }: HomeProps) {
return (
<div className="container">
<h1>SpaceX launches</h1>
<Suspense
fallback={<h4>Fetching launches...</h4>}
key={searchParams.offset}
>
<Launches {...searchParams} />
</Suspense>
</div>
);
}
export type HomeProps = { searchParams: { offset: string | null } };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment