I want to navigate to the same route, but preserve all search params on link click and change only the page
.
I am using Material-UI's Pagination
component and React-Router-DOM's Link
.
I have tried spreading the existng search params and changing only the page
, but it removes all params and appends only the page one.
import Divider from '@mui/material/Divider';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import Pagination from '@mui/material/Pagination';
import { useDataQuery } from '../../api';
import { itemsPerPage } from '../../constants';
import { PaginationItem } from '@mui/material';
import {
Link,
createSearchParams,
useLocation,
useSearchParams
} from 'react-router-dom';
export function PaginatedList() {
const { data } = useDataQuery();
const [searchParams, setSearchParams] = useSearchParams();
const location = useLocation();
const currentPage = Number(searchParams.get('page')) || 1;
const pageCount = data?.total ? Math.ceil(data?.total / itemsPerPage) : 1;
return (
<Box>
<List>
{data.items.map((dataItem) => (
<ListItem key={dataItem.id}>
<Card item={dataItem} />
</ListItem>
))}
</List>
<Divider />
<Box>
<Pagination
defaultPage={1}
page={currentPage}
count={pageCount}
renderItem={(item) => (
<PaginationItem
component={Link}
to={{
pathname: location.pathname,
search:
item.page === 1
? ''
: createSearchParams({
...searchParams,
page: item.page?.toString(),
}).toString(),
}}
{...item}
/>
)}
/>
</Box>
</Box>
);
}
I have also tried setting the query like this, but it does not seem to do anything (it is not appending any params to the pathname):
to={{
pathname: location.pathname,
query: {
...searchParams,
page: item.page,
},
}}
Here is the link to the working codesandbox demo.
The PaginationItem
component appears to close over a stale copy of the search params and it doesn't appear as though you can "intercept" the Link
component's onClick
event handler to manually handle the search params because it's the PaginationItem
component's click handler that it needs to effect the page change.
Best suggestion I can think of is to use the Pagination
component's onChange
handler to synchronize the URL search params to the changed page value.
Example:
<Pagination
defaultPage={1}
page={currentPage}
count={pageCount}
onChange={(e, page) => {
setSearchParams((searchParams) => {
if (page === 1) {
searchParams.delete("page");
} else {
searchParams.set("page", page);
}
return searchParams;
});
}}
renderItem={(item) => <PaginationItem {...item} />}
/>
If you must use a Link
component as the pagination item then it would seem the issue is trying to spread the searchParams
object into a new URLSearchParams
object, it doesn't correctly shallow copy the search parameters. Move the logic of computing a new search string outside the to
prop.
Example:
<Pagination
defaultPage={1}
page={currentPage}
count={pageCount}
renderItem={(item) => {
const search = createSearchParams(searchParams);
if (item.page === 1) {
search.delete("page");
} else {
search.set("page", item.page);
}
return (
<PaginationItem
component={Link}
to={{
pathname: ".",
search: search.toString()
}}
{...item}
/>
);
}}
/>