nextui
nextui copied to clipboard
[BUG] - Inconsistent URL parameters when using useSearchParams with pagination
NextUI Version
latest
Describe the bug
Issue:
When using useSearchParams
from next/navigation
to implement pagination, the URL parameters are not consistently updated.
Steps to reproduce:
- Implement pagination using
useSearchParams
to get the current page number from the URL's query parameters. - Navigate to a new page
Your Example Website or App
No response
Steps to Reproduce the Bug or Issue
Just copy the code and try this pagination component.
Expected behavior
Expected behavior:
The URL should always include both the page and sort parameters, regardless of whether you're navigating to a new page or changing the sort option.
expected
?_sort=price&_order=asc&_page=3
?_sort=price&_order=asc&_page=4
?_sort=price&_order=asc&_page=5
but got
?_sort=price&_order=asc&_page=1
?__page=2
?_sort=price&_order=asc&_page=3
Actual behavior: The URL does not consistently include both the page and sort parameters. When navigating to a new page, the sort parameters are sometimes missing from the URL, and vice versa.
Screenshots or Videos
Ok first see the difference
This is the video where I use nextui
https://github.com/nextui-org/nextui/assets/97989643/82803b27-789c-4df7-821e-248ad3a2a2f1
Actual Code with nextui
import { ITEMS_PER_PAGE } from "@/constant/constants";
import React from "react";
import { Pagination } from "@nextui-org/react";
import { useRouter, useSearchParams } from "next/navigation";
import { useAppSelector } from "@/store/redux/useSelector";
import { selectTotalItems } from "@/lib/features/product/product-pc-slice";
import PaginationSkeleton from "@/loader/skeleton/product-t1/pagination-skeleton";
import { defaultUrlPagination } from "@/links/product-list";
export function PaginationPage() {
const searchParams = useSearchParams();
const page = parseInt(searchParams.get("_page") || "1");
const totalItems = useAppSelector(selectTotalItems);
const totalPages = Math.ceil(totalItems / ITEMS_PER_PAGE);
const router = useRouter();
const handlePageChange = (newPage: number) => {
const newSearchParams = new URLSearchParams(searchParams.toString());
newSearchParams.set("_page", newPage.toString());
router.push(`${defaultUrlPagination}${newSearchParams.toString()}`);
};
if (!totalItems) {
return <PaginationSkeleton />;
}
return (
<div className="flex items-center justify-between border-t border-gray-200 px-4 py-3 sm:px-6">
<div className="hidden lg:block">
<p className="text-sm text-gray-700 dark:text-gray-200">
Showing
<span className="font-medium">
{(page - 1) * ITEMS_PER_PAGE + 1}
</span>{" "}
to
<span className="font-medium">
{page * ITEMS_PER_PAGE > totalItems
? totalItems
: page * ITEMS_PER_PAGE}
</span>
of <span className="font-medium">{totalItems}</span> results
</p>
</div>
<Pagination
initialPage={1}
showControls
showShadow
boundaries={2}
total={totalPages || 1}
page={page || 1}
onChange={handlePageChange}
color="primary"
variant="bordered"
/>
</div>
);
}
Upon implementing the same approach with Shadcn, I observed that it works as expected.
https://github.com/nextui-org/nextui/assets/97989643/a9c5223e-51f2-48a0-8672-ab858a95d211
import React from "react";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/shadcn/pagination";
import { ITEMS_PER_PAGE } from "@/constant/constants";
import { Button } from "@/components/ui/shadcn/button";
import { useAppSelector } from "@/store/redux/useSelector";
import { selectTotalItems } from "@/lib/features/product/product-pc-slice";
import { useRouter, useSearchParams } from "next/navigation";
import PaginationSkeleton from "@/loader/skeleton/product-t1/pagination-skeleton";
import { defaultUrlPagination } from "@/links/product-list";
export function PaginationPage() {
const searchParams = useSearchParams();
const page = parseInt(searchParams.get("_page") || "1");
const totalItems = useAppSelector(selectTotalItems);
const totalPages = Math.ceil(totalItems / ITEMS_PER_PAGE);
const router = useRouter();
const handlePageChange = (newPage: number) => {
const newSearchParams = new URLSearchParams(searchParams.toString());
newSearchParams.set("_page", newPage.toString());
router.push(`${defaultUrlPagination}${newSearchParams.toString()}`);
};
if (!totalItems) {
return <PaginationSkeleton />;
}
return (
<div className="flex flex-col sm:flex-row justify-between items-center">
<p className="text-sm text-gray-700 dark:text-gray-200 mb-2 sm:mb-0">
Showing
<span className="font-medium">{(page - 1) * ITEMS_PER_PAGE + 1}</span>
to
<span className="font-medium">
{page * ITEMS_PER_PAGE > totalItems
? totalItems
: page * ITEMS_PER_PAGE}
</span>{" "}
of <span className="font-medium">{totalItems}</span> results
</p>
<Pagination>
<PaginationContent>
<PaginationItem>
<Button
variant="secondary"
onClick={() => handlePageChange(page > 1 ? page - 1 : page)}
>
Previous
</Button>
</PaginationItem>
{Array.from({ length: totalPages }).map((_, index) => (
<PaginationItem key={index}>
<PaginationLink
href="#"
isActive={index + 1 === page}
onClick={() => handlePageChange(index + 1)}
>
{index + 1}
</PaginationLink>
</PaginationItem>
))}
{page < totalPages && <PaginationEllipsis />}
<PaginationItem>
<Button
variant="secondary"
onClick={() =>
handlePageChange(page < totalPages ? page + 1 : page)
}
>
Next
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
}
Operating System Version
windows, mac
Browser
Chrome
Bump - I have ran into the same issue using Remix. It took 2 days, but eventually tracked it down to this Pagination component. Not sure how it interferes with useSearchParams exactly, but, I wonder if it has something to do with some internal caching of the onChange event?
Also just run into that problem. Took me like 3 hours to realise that the problemis related to this component. The problem seems to be somehow related to the onChange.
<Pagination
isCompact
showControls
showShadow
total={loaderData.meta.totalPages}
onChange={(page) => setPage(page)}
/>
When I use external buttons with the same function, everything works.
<Button onClick={() => setPage(loaderData.meta.currentPage - 1)}>prev</Button>
EDIT:
I was able to workaround this problem using a useState.
const [pageState, setPageState] = useState<{
prevPage: number;
currentPage: number;
}>({
prevPage: loaderData.meta.currentPage,
currentPage: loaderData.meta.currentPage,
});
useEffect(() => {
if (pageState.prevPage !== pageState.currentPage) {
setPageState({
prevPage: pageState.currentPage,
currentPage: pageState.currentPage,
});
setPage(pageState.currentPage);
}
}, [pageState, setPage]);
<Pagination
isCompact
showControls
showShadow
initialPage={loaderData.meta.currentPage}
page={pageState.currentPage}
total={loaderData.meta.totalPages}
onChange={(page) =>
setPageState({
prevPage: pageState.currentPage,
currentPage: page,
})
}
/>