Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] - Inconsistent URL parameters when using useSearchParams with pagination #2417

Open
Subham-Maity opened this issue Feb 29, 2024 · 3 comments
Labels
🐛 Type: Bug Something isn't working

Comments

@Subham-Maity
Copy link

Subham-Maity commented Feb 29, 2024

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:

  1. Implement pagination using useSearchParams to get the current page number from the URL's query parameters.
  2. 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

Recording.2024-02-29.114732.mp4

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.

Recording.2024-02-29.115159.mp4
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

@Subham-Maity Subham-Maity added the 🐛 Type: Bug Something isn't working label Feb 29, 2024
Copy link

linear bot commented Feb 29, 2024

@jesuzon
Copy link
Contributor

jesuzon commented May 1, 2024

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?

@sharenz
Copy link

sharenz commented May 26, 2024

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,
    })
    }
/>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 Type: Bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants