0

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.

Codesandbox

Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • `getUsers` in `onPageChanged` will always be the one that got *created* in the current closure and cannot be changed by a state update. Your question is basically the same as this one: https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately It's not antd or react, it's plain javascript closures. – Yoshi Dec 13 '21 at 11:02

1 Answers1

1

The best way of doing it is to get the last value with the prevState. And you should watch when pagination change for trigger your getUsers (with a useEffect), working example with useEffect

Or alternatively, you can just pass a few arguments to your getUsers method : working example with parameter to getUser

Sephyre
  • 326
  • 2
  • 13
  • Yeah. My current solution is to pass the new `pagination` state as the parameter of `getUsers()`. Actually, I am looking for a solution except for this way. But still thank you, give you an upvote. – Lin Du Dec 13 '21 at 11:34
  • The other way is to do it with useEffect. As in the first example. – Sephyre Dec 13 '21 at 12:14