Skip to content

Commit

Permalink
cleaner cursor based pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
collinschaafsma committed Oct 29, 2024
1 parent 88a2286 commit 93d62fe
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 35 deletions.
59 changes: 47 additions & 12 deletions app/(app)/account/_components/paginator-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client"

import { createContext, ReactNode, use } from "react"
import { useSearchParams } from "next/navigation"
import { createContext, ReactNode, use, useEffect, useState } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import Stripe from "stripe"

interface GenericData {
Expand All @@ -12,8 +12,8 @@ export interface PaginatorContextInterface {
showPaginator: boolean
previousPageDisabled: boolean
nextPageDisabled: boolean
nextPage: string | null
previousPage: string | null
handleNextPage: () => void
handlePrevPage: () => void
}

export const PaginatorContext = createContext<PaginatorContextInterface | null>(
Expand All @@ -28,25 +28,60 @@ export function PaginatorProvider<T extends GenericData>({
responseToPaginatePromise: Promise<Stripe.Response<Stripe.ApiList<T>> | null>
}>) {
const responseToPaginate = use(responseToPaginatePromise)
const router = useRouter()
const searchParams = useSearchParams()
const [hasMore, setHasMore] = useState(true)
const [prevCursors, setPrevCursors] = useState<string[]>([])
const cursor = searchParams.get("cursor")
const direction = searchParams.get("direction")

const previousPageDisabled = !cursor
const nextPageDisabled = !responseToPaginate?.has_more
useEffect(() => {
if (cursor) {
setPrevCursors(prev => [...prev, cursor])
} else {
setPrevCursors([])
}

setHasMore(responseToPaginate?.has_more || false)
}, [cursor])

const handleNextPage = () => {
if (responseToPaginate?.data.length) {
const lastCustomerId =
responseToPaginate.data[responseToPaginate.data.length - 1].id
const params = new URLSearchParams(searchParams)
params.set("cursor", lastCustomerId)
router.push(`?${params.toString()}`)
}
}

const handlePrevPage = () => {
if (prevCursors.length > 0) {
const newPrevCursors = [...prevCursors]
const prevCursor = newPrevCursors.pop() // Remove the current cursor
setPrevCursors(newPrevCursors)

const params = new URLSearchParams(searchParams)
if (prevCursor && newPrevCursors.length > 0) {
params.set("cursor", newPrevCursors[newPrevCursors.length - 1])
} else {
params.delete("cursor")
}
router.push(`?${params.toString()}`)
}
}

const previousPageDisabled = prevCursors.length === 0
const nextPageDisabled = !hasMore
const showPaginator = !previousPageDisabled && !nextPageDisabled
const nextPage = responseToPaginate?.data?.[0]?.id ?? null
const previousPage =
responseToPaginate?.data?.[responseToPaginate?.data?.length - 1]?.id ?? null

return (
<PaginatorContext.Provider
value={{
showPaginator,
previousPageDisabled,
nextPageDisabled,
nextPage,
previousPage,
handleNextPage,
handlePrevPage,
}}
>
{children}
Expand Down
37 changes: 14 additions & 23 deletions app/(app)/account/_components/paginator.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,43 @@
"use client"

import Link from "next/link"
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { usePaginator } from "./use-paginator"

export function Paginator() {
const { previousPageDisabled, nextPageDisabled, previousPage, nextPage } =
usePaginator()
const {
previousPageDisabled,
nextPageDisabled,
handlePrevPage,
handleNextPage,
} = usePaginator()

return (
<div className="flex w-full items-center justify-center space-x-2">
<Button
asChild
onClick={handlePrevPage}
disabled={previousPageDisabled}
variant="outline"
className={cn(
"size-8 p-0",
previousPageDisabled && "bg-muted text-muted-foreground"
)}
>
<Link
href={`/account?cursor=${previousPage}&direction=backward`}
aria-disabled={previousPageDisabled}
tabIndex={previousPageDisabled ? -1 : 0}
className={previousPageDisabled ? "pointer-events-none" : ""}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="size-4" />
</Link>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="size-4" />
</Button>
<Button
asChild
onClick={handleNextPage}
disabled={nextPageDisabled}
variant="outline"
className={cn(
"size-8 p-0",
nextPageDisabled && "bg-muted text-muted-foreground"
)}
>
<Link
href={`/account?cursor=${nextPage}&direction=forward`}
aria-disabled={nextPageDisabled}
tabIndex={nextPageDisabled ? -1 : 0}
className={nextPageDisabled ? "pointer-events-none" : ""}
>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="size-4" />
</Link>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="size-4" />
</Button>
</div>
)
Expand Down

0 comments on commit 93d62fe

Please sign in to comment.