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
updatePaginationParams
: This function is responsible for updating the URL with the newpage
andpageSize
whenever the user navigates to a different page.navigate()
: Instead of usingLink
components, we directly callnavigate()
(fromreact-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.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. Thedisabled
attribute prevents users from navigating beyond the available pages.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 withonClick
handlers for pagination because it’s built to handle navigation on its own. Adding custom behavior withonClick
might interfere with its internal logic, especially when you’re trying to manipulate URL params manually.navigate()
: Thenavigate()
function fromreact-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!