reactjsmaterial-uipaginationreact-router-domurlsearchparams

Updating ONLY one search param using react-router-dom Link


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.


Solution

  • 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} />}
    />
    

    Edit updating-only-one-search-param-using-react-router-dom-link

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

    Edit updating-only-one-search-param-using-react-router-dom-link (forked)