I know this question has been asked many times, and I know how useCallback works, but my question is related to antd UI framework(Or maybe it doesn't matter). I made a minimal, reproducible example:
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Form, Input, Button, Table } from "antd";
import faker from "faker";
const mockApi = {
  async getUsers({ country, current, pageSize }) {
    return {
      total: 100,
      users: Array.from({ length: 10 }).map((_) => ({
        id: faker.random.uuid(),
        email: faker.internet.email(),
        country: "US"
      }))
    };
  }
};
const initialPagination = { current: 1, pageSize: 10 };
function App() {
  const [form] = Form.useForm();
  const [pagination, setPagination] = useState(initialPagination);
  const [users, setUsers] = useState([]);
  const [total, setTotal] = useState(0);
  useMemo(() => {
    console.log("render pagination: ", pagination);
  }, [pagination]);
  const getUsers = useCallback(async () => {
    console.log("getUsers pagination: ", pagination);
    const params = {
      ...pagination,
      country: form.getFieldValue("country")
    };
    const getUsersResponse = await mockApi.getUsers(params);
    setUsers(getUsersResponse.users);
    setTotal(getUsersResponse.total);
  }, [pagination]);
  const onPageChanged = useCallback(
    (current) => {
      console.log("change current page to: ", current);
      setPagination({ ...pagination, current });
      getUsers();
    },
    [pagination, getUsers]
  );
  return (
    <div>
      <h1>Test</h1>
      <Form form={form} initialValues={{ email: "" }} onFinish={getUsers}>
        <Form.Item name="country" label="country">
          <Input type="text" />
        </Form.Item>
        <Form.Item>
          <Button htmlType="submit">submit</Button>
        </Form.Item>
      </Form>
      <Table
        pagination={{ ...pagination, onChange: onPageChanged, total }}
        dataSource={users}
        columns={[{ title: "email", dataIndex: "email" }]}
        rowKey="id"
      />
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById("root"));
Step 1: Submit the form
Step 2: Change the current page to 2.
Step 3: Change the current page to 3.
The logs:
render pagination:  {current: 1, pageSize: 10}
getUsers pagination:  {current: 1, pageSize: 10}
change current page to:  2
getUsers pagination:  {current: 1, pageSize: 10}
render pagination:  {current: 2, pageSize: 10}
change current page to:  3
getUsers pagination:  {current: 2, pageSize: 10}
render pagination:  {current: 3, pageSize: 10}
The the value of pagiation.current in getUsers function is NOT the latest value. It's always the previous value. I have used the pagination as the dependency of the getUsers function. So I think when the onPageChanged event handler is triggered, after setting a new pagination state, the getUsers() function should be re-created with the latest value of deps.
I kind of lost track of why. I know I can use functional updates of useState instead of useCallback + deps. But I insist to use useCallback + deps way.