How to reset the Pagination's current page when pageSize changes in Ant Design?

Viewed 3128

My requirement: when pageSize changes (with onShowSizeChange), reset the current to 1, instead of preserving the current (like the default behavior). How to do that with Ant Design Pagination?

E.g: I'm in pageSize=10,current=2, when changing pageSize from 10 to 20, I want current to be reset to 1, instead of still being 2.

My code:

function App() {
  const [current, setCurrent] = React.useState(1);

  return (
    <Pagination
      current={current}
      onChange={setCurrent}
      showSizeChanger={true}
      onShowSizeChange={() => setCurrent(1)}
      total={1000}
    />
  );
}

CodeSandbox

3 Answers

Its a simple issue that gets really annoying with AntD

The onChange fires whenever anything changes in the pagination not only when changing page number. Meaning, when you change the page size, it triggers both onShowSizeChange AND onChange. But onChange has highter priority hence it triggers last. So it sets the page back to the same one.

Now there are multiple fixes to do. For me, personalty, I do not use antd provided page sizer in pagination and i use my own custom made using select. But there are other solutions.

If you use onChange purply only for page changing then there is a simple solution - filter if the page actually changed.

In this case, if you change your page, the provided variable on onChange and your state variable current will not match, but if you change the page size, the page will remain the same with current. This allows to do a simple if check.

onChange={(pageNum) => pageNum !== current && setCurrent(pageNum)}

This way if the page number and the current page number is the same, onChange will not change the page number (to the same one) and trigger a re-render. Its good, because it also saves on performance to not re-trigger same re-renders when nothing changed. This also allows for the onShowSizeChange to kick in without being overridden.

<Pagination
      current={current}
      onChange={(pageNum) => pageNum !== current && setCurrent(pageNum)}
      showSizeChanger={true}
      onShowSizeChange={() => setCurrent(1)}
      total={1000}
/>

BUT there is an issue. If you are on the last page and you change the page size - the page number will be different, by antD calculations, and hence the if will not prevent it. So the first method would be a better one.

There is, also, a 3rd method, that I can think of. But its a hacky one. Would not use it in a big App. But its not bad. Adding 2 new methods. sizeChangeLogic that is responsible for size change logic code.

const sizeChangeLogic = (current, size) => {
    setSize = true;
    setCurrent(1);
  };

and onChangeLogic method for on change logic code

const onChangeLogic = (current, size) => {
    !setSize && setCurrent(current);
  };

and also include a new variable let setSize = false; after useState.

NOTE this new variable canNOT be a state. Ill explain why soon.

and the pagination itself:

<Pagination
  current={current}
  onShowSizeChange={sizeChangeLogic}
  onChange={onChangeLogic}
  showSizeChanger={true}
  total={1000}
/>

The logic here is, since onShowSizeChange triggers first, will set the locla variable setSize to true and set the page num to 1. Then onChange triggers and gets the setSize value that is, rn true, invert it and check if it can change the number if its false then it can. If its true (after a size change) it will not change it. This prevents the previously mentioned bug. After the page number (current) changes, it triggers a re-render that triggers the setSizeto go back tofalse` state hence the cycle continues.

The reason you can NOT have setSize as a state is because the state value changes AFTER a re-render. So the value from false to true would not change as both onchange methods would be ran before a re-render.

Heres a 3rd method full code:

function App() {
  const [current, setCurrent] = React.useState(1);
  let setSize = false;

  const sizeChangeLogic = (current, size) => {
    setSize = true;
    setCurrent(1);
  };

  const onChangeLogic = (current, size) => {
    !setSize && setCurrent(current);
  };

  return (
    <Pagination
      current={current}
      onShowSizeChange={sizeChangeLogic}
      onChange={onChangeLogic}
      showSizeChanger={true}
      total={1000}
    />
  );
}

Choose what fits you best.

This is what happens inside Pagination when the pageSize value is changed:

this.props.onShowSizeChange(current, size);

if ('onChange' in this.props && this.props.onChange) {
  this.props.onChange(current, size);
}

Here is a picture to illustrate what happens:

enter image description here

Because onChange callback notifies when the value of either the current page or pageSize changes, you can control the pageSize value and only reset the current page if the pageSize is different in the last render:

const [current, setCurrent] = React.useState(1);
const [pageSize, setPageSize] = React.useState(10);

console.log({ current });

return (
  <Pagination
    current={current}
    pageSize={pageSize}
    onChange={(newPage, newPageSize) => {
      // react batches all setState calls internally so this only costs 1 render
      setPageSize(newPageSize);
      setCurrent(pageSize !== newPageSize ? 1 : newPage);
    }}
    showSizeChanger={true}
    total={1000}
  />
);

Codesandbox Demo

Many thanks to all the answerers, all are very insightful and really helped me to solve the problem. Cause my real code involves storing pageSize as a state as well, here's my final solution based on their answers (which will get rid of onShowSizeChange altogether):

function App() {
  const [current, setCurrent] = React.useState(1);
  const pageSizeRef = React.useRef(10);

  return (
    <Pagination
      current={current}
      defaultPageSize={pageSizeRef.current}
      onChange={(newCurrent, newPageSize) => {
        const pageSizeChange = pageSizeRef.current !== newPageSize;
        if (pageSizeChange) {
          setCurrent(1);
        } else {
          setCurrent(newCurrent);
        }
        pageSizeRef.current = newPageSize;
      }}
      showSizeChanger={true}
      total={1000}
    />
  );
}

Re-explanation:

Look at the internal implementation, when changePageSize, it'll call props.onShowSizeChange and props.onChange with the order of props.onShowSizeChange first and props.onChange later. And I had setCurrent inside props.onChange as well so it overrode the setCurrent(1) in props.onShowSizeChange. So the possible solutions are:

  1. Make the setCurrent inside props.onShowSizeChange being fired after with setTimeout (thanks @nearhuscarl)
  2. Ignore handling for pageSize changes inside onChange and only handle it in onShowSizeChange (thanks @lith)
  3. Handle pageSize changes inside onChange instead of handling it in onShowSizeChange (what I ended up with)
Related