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

199 views Asked by At

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.

1

There are 1 answers

8
Drew Reese On BEST ANSWER

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)