Hey Buddy,

I recently ran into a problem while building pagination for a table in a React app. My goal was to have pagination state (like page and pageSize) persist in the URL so the page doesn’t reset on refresh. You know, so if the user navigates away and comes back, they can see the same page of results they left off on. Easy, right? But React Router's Link component was causing me some trouble, so I had to come up with a workaround.

Let me walk you through what I encountered and how I solved it.

The Issue

I had a simple table with pagination. The idea was to update the URL whenever the user changed pages or adjusted the page size. This would make the table "bookmarkable" (i.e., a user could copy-paste the URL and return to the exact same page). Here's the catch: When using Link to update the page number in the URL, it wasn't working properly.

React Router’s Link is great for navigation, but when you try to control it manually (for example, using onClick to change URL params), it doesn’t handle everything the way I expected. Specifically, when I used Link to trigger pagination changes (like moving to the next page or switching page sizes), the URL didn’t update as expected, and the page didn’t reflect the right data.

The Solution

The solution was to ditch Link for the pagination buttons and instead use a button with navigate() from react-router-dom to manually handle the URL update.

Let’s break this down step-by-step.

Step 1: Create a Custom Hook for URL Params

First, I created a custom hook usePaginationParams that would handle updating the URL params based on the page and page size:

import { useNavigate, useLocation } from "react-router-dom";

function usePaginationParams() {
  const navigate = useNavigate();
  const location = useLocation();

  const updatePaginationParams = ({ page, pageSize }) => {
    const params = new URLSearchParams(location.search);

    if (page !== undefined) params.set("page", page);
    if (pageSize !== undefined) params.set("pageSize", pageSize);

    const newPath = `${location.pathname}?${params.toString()}`;
    console.log(newPath);  // Debugging: log the new URL

    navigate(newPath, { replace: true });  // Update the URL without reloading the page
  };

  return { updatePaginationParams };
}

export default usePaginationParams;

Step 2: Handling Pagination and Updating the URL

Next, I had to handle pagination in the table and update the URL whenever the user changed the page or page size. Instead of using Link for pagination buttons, I used a regular button and directly called navigate().

Here’s the relevant part of the code:

const TableContainer = ({ columns, data, total, pageIndex = 0, setPagination, customPageSize = 10 }) => {
  const { updatePaginationParams } = usePaginationParams();
  const [currentPage, setCurrentPage] = useState(pageIndex);

  // Handle page change
  const handlePageChange = (newPage) => {
    setCurrentPage(newPage);
    updatePaginationParams({ page: newPage + 1, pageSize: customPageSize });
    setPagination(newPage);  // Update pagination state
  };

  // Calculate pagination details
  const totalPages = Math.ceil(total / customPageSize);

  return (
    <div>
      <div>
        <button
          onClick={() => handlePageChange(currentPage - 1)}
          disabled={currentPage === 0}
        >
          Previous
        button>
        {Array.from({ length: totalPages }, (_, index) => (
          <button
            key={index}
            onClick={() => handlePageChange(index)}
            className={currentPage === index ? 'active' : ''}
          >
            {index + 1}
          button>
        ))}
        <button
          onClick={() => handlePageChange(currentPage + 1)}
          disabled={currentPage === totalPages - 1}
        >
          Next
        button>
      div>
    div>
  );
};

Step 3: Explanation of the Fix

  1. updatePaginationParams: This function is responsible for updating the URL with the new page and pageSize whenever the user navigates to a different page.

  2. navigate(): Instead of using Link components, we directly call navigate() (from react-router-dom) to update the URL and avoid page reloads. This is what allows the page URL to stay in sync with the table's current state.

  3. Buttons for Pagination: I replaced Link components with regular buttons (). These buttons trigger the URL change using handlePageChange, which updates both the state of the page and the URL params. The disabled attribute prevents users from navigating beyond the available pages.

  4. State Handling: The currentPage state keeps track of the active page, ensuring that the page navigation buttons reflect the current page in the UI.

Why This Works

  • Link vs Button: Link was not working as expected with onClick handlers for pagination because it’s built to handle navigation on its own. Adding custom behavior with onClick might interfere with its internal logic, especially when you’re trying to manipulate URL params manually.

  • navigate(): The navigate() function from react-router-dom is designed to update the URL without reloading the page. It’s exactly what we needed for our pagination because it allows us to change the URL params while keeping the user on the same page (without triggering a full reload).

  • Control over the URL: Using navigate() directly allows us full control over the URL and ensures it reflects the current page and page size, which is crucial for bookmarking and sharing URLs.

Conclusion

By replacing Link with button and using navigate() for manual URL updates, I was able to fix the pagination issue and ensure the URL stays in sync with the pagination state. It was a simple but effective solution, and it works seamlessly in React Router.

Now, when a user changes pages, the URL is updated accordingly, and the state persists even if the page is refreshed. I’m sharing this with you in case you run into a similar issue in your projects!

Hope this helps!