从 Promise 返回响应后获取本地状态变量的陈旧值

问题描述 投票:0回答:1

我有一个带有两个按钮的反应应用程序,单击后会从服务器加载用户名。如果我一次单击一个按钮并等待响应,则该行为有效,但是,如果我同时单击两个按钮,第二个按钮的 API 响应会将值写入已过时的状态,因为第一个按钮会卡在加载状态。我该如何解决这个问题,以便在承诺解决时始终拥有最新数据?

代码沙箱演示:https://codesandbox.io/s/pense-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>
  );
}

javascript reactjs promise closures
1个回答
1
投票

问题在于 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
      });
    });
  };

这里是一个 React 游乐场,带有简化的工作版本。

© www.soinside.com 2019 - 2024. All rights reserved.