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.