从承诺返回响应后,获得局部状态变量的过时价值

发布于 2025-02-03 08:01:24 字数 2008 浏览 2 评论 0 原文

我有一个带有两个按钮的React应用程序,该按钮在单击服务器上单击加载用户名。如果我一次单击一个按钮,并且等待响应,则行为有效,但是,如果我单击两者,则API的第二个按钮的响应将其写入值对状态的值,该状态是陈旧的,因此第一个按钮被卡在加载状态下。当Promise解决时,如何解决这个问题始终拥有最新数据?

代码沙盒演示:

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>
  );
}

I have a react application with two buttons, which on click load user name from server. The behaviour works if I click buttons one at a time and wait for response, however, if I click both, the response from API for second button writes value to state which is stale due to which the first button gets stuck in loading state. How can I resolve this to always have latest data when promise resolves?

Code sandbox demo: 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>
  );
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

前事休说 2025-02-10 08:01:24

问题在于,Usestate的设置器是异步的,因此,在加载程序函数中,当您定义 const undedusers = {... users}; 时,没有必要更新用户。

幸运的是,Usestate的设定器提供了使我们能够访问以前的状态。
If you refactor your code like this, it should work:

  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 with a simplified working version.

The problem is that useState's setter is asynchronous, so, in your loader function, when you define const updatedUsers = { ...users };, user is not necessary updated.

Luckily, useState's setter provides allows us to access to the previous state.
If you refactor your code like this, it should work:

  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 with a simplified working version.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文