从 promise 返回响应后获取本地状态变量的陈旧值
Getting stale value of local state variable after response returned from promise
我有一个带有两个按钮的 React 应用程序,单击它们可以从服务器加载用户名。如果我一次单击一个按钮并等待响应,则该行为有效,但是,如果我同时单击两个按钮,来自 API 的第二个按钮的响应会将值写入陈旧状态,因为第一个按钮卡在加载状态。我如何解决此问题以在 promise 解决时始终拥有最新数据?
代码沙箱演示:https://codesandbox.io/s/pensive-frost-qkm9xh?file=/src/App.js:0-1532
import "./styles.css";
import LoadingButton from "@mui/lab/LoadingButton";
import { useRef, useState } from "react";
import { Typography } from "@mui/material";
const getUsersApi = (id) => {
const users = { "12": "John", "47": "Paul", "55": "Alice" };
return new Promise((resolve) => {
setTimeout((_) => {
resolve(users[id]);
}, 1000);
});
};
export default function App() {
const [users, setUsers] = useState({});
const availableUserIds = [12, 47];
const loadUser = (userId) => {
// Mark button as loading
const updatedUsers = { ...users };
updatedUsers[userId] = {
id: userId,
name: undefined,
isLoading: true,
isFailed: false
};
setUsers(updatedUsers);
// Call API
getUsersApi(userId).then((userName) => {
// Update state with user name
const updatedUsers = { ...users };
updatedUsers[userId] = {
...updatedUsers[userId],
name: userName,
isLoading: false,
isFailed: false
};
setUsers(updatedUsers);
});
};
return (
<div className="App">
{availableUserIds.map((userId) =>
users[userId]?.name ? (
<Typography variant="h3">{users[userId].name}</Typography>
) : (
<LoadingButton
key={userId}
loading={users[userId]?.isLoading}
variant="outlined"
onClick={() => loadUser(userId)}
>
Load User {userId}
</LoadingButton>
)
)}
</div>
);
}
问题是 useState 的 setter 是异步的,因此,在您的加载程序函数中,当您定义 const updatedUsers = { ...users };
时,用户不需要更新。
幸运的是,useState 的 setter 允许我们访问之前的状态。
如果您像这样重构代码,它应该可以工作:
const loadUser = (userId) => {
// Mark button as loading
const updatedUsers = { ...users };
updatedUsers[userId] = {
id: userId,
name: undefined,
isLoading: true,
isFailed: false
};
setUsers(updatedUsers);
// Call API
getUsersApi(userId).then((userName) => {
// Update state with user name
setUsers(prevUsers => {
const updatedUsers = { ...prevUsers };
updatedUsers[userId] = {
...updatedUsers[userId],
name: userName,
isLoading: false,
isFailed: false
};
return updatedUsers
});
});
};
Here a React playground 具有简化的工作版本。
我有一个带有两个按钮的 React 应用程序,单击它们可以从服务器加载用户名。如果我一次单击一个按钮并等待响应,则该行为有效,但是,如果我同时单击两个按钮,来自 API 的第二个按钮的响应会将值写入陈旧状态,因为第一个按钮卡在加载状态。我如何解决此问题以在 promise 解决时始终拥有最新数据?
代码沙箱演示:https://codesandbox.io/s/pensive-frost-qkm9xh?file=/src/App.js:0-1532
import "./styles.css";
import LoadingButton from "@mui/lab/LoadingButton";
import { useRef, useState } from "react";
import { Typography } from "@mui/material";
const getUsersApi = (id) => {
const users = { "12": "John", "47": "Paul", "55": "Alice" };
return new Promise((resolve) => {
setTimeout((_) => {
resolve(users[id]);
}, 1000);
});
};
export default function App() {
const [users, setUsers] = useState({});
const availableUserIds = [12, 47];
const loadUser = (userId) => {
// Mark button as loading
const updatedUsers = { ...users };
updatedUsers[userId] = {
id: userId,
name: undefined,
isLoading: true,
isFailed: false
};
setUsers(updatedUsers);
// Call API
getUsersApi(userId).then((userName) => {
// Update state with user name
const updatedUsers = { ...users };
updatedUsers[userId] = {
...updatedUsers[userId],
name: userName,
isLoading: false,
isFailed: false
};
setUsers(updatedUsers);
});
};
return (
<div className="App">
{availableUserIds.map((userId) =>
users[userId]?.name ? (
<Typography variant="h3">{users[userId].name}</Typography>
) : (
<LoadingButton
key={userId}
loading={users[userId]?.isLoading}
variant="outlined"
onClick={() => loadUser(userId)}
>
Load User {userId}
</LoadingButton>
)
)}
</div>
);
}
问题是 useState 的 setter 是异步的,因此,在您的加载程序函数中,当您定义 const updatedUsers = { ...users };
时,用户不需要更新。
幸运的是,useState 的 setter 允许我们访问之前的状态。 如果您像这样重构代码,它应该可以工作:
const loadUser = (userId) => {
// Mark button as loading
const updatedUsers = { ...users };
updatedUsers[userId] = {
id: userId,
name: undefined,
isLoading: true,
isFailed: false
};
setUsers(updatedUsers);
// Call API
getUsersApi(userId).then((userName) => {
// Update state with user name
setUsers(prevUsers => {
const updatedUsers = { ...prevUsers };
updatedUsers[userId] = {
...updatedUsers[userId],
name: userName,
isLoading: false,
isFailed: false
};
return updatedUsers
});
});
};
Here a React playground 具有简化的工作版本。