我在显示单个数据项的组件(InternalComponent)中有两个useEffect挂钩.一个useEffect将计数作为状态进行跟踪,当用户增加计数时将其发送到数据库.第二个useEffect在InternalComponent跟踪的项目发生更改(由于外部操作)时重置计数.

问题是:更新数据库的useEffect会在项目更改时触发;它将看到新的项目值,但会错误地向数据库发送previous个项目的计数.发生这种情况是因为两个useEffects"同时"触发,并且在运行POST useEffect之前,不应设置指示计数的状态.

const InternalComponent = ({ item }) => {
  const [count, setCount] = useState(item.count);
  const [countChanged, setCountChanged] = useState(false);

  useEffect(() => {
    console.log(
      `Item is now ${item.id}; setting count from item and marking unchanged.`
    );
    setCount(item.count);
    setCountChanged(false);
  }, [item]);

  useEffect(() => {
    if (countChanged) {
      console.log(
        `Count changed for item ${item.id}, POSTing (id=${item.id}, count=${count}) to database.`
      );
    } else {
      console.log(
        `Count hasn't changed yet, so don't update the DB (id=${item.id}, count=${count})`
      );
    }
  }, [item, count, countChanged]);

  const handleButtonClick = () => {
    setCount(count + 1);
    setCountChanged(true);
  };

  return (
    <div>
      I'm showing item {item.id}, which has count {count}.<br />
      <button onClick={handleButtonClick}>Increment item count</button>
    </div>
  );
};

代码Sandbox 上的最小工作示例:https://codesandbox.io/s/post-with-stale-data-vfzh4j?file=/src/App.js

注释输出:

1 (After button click) Count changed for item 1, POSTing (id=1, count=6) to database. 
2 (After item changed) Item is now 2; setting count from item and marking unchanged. 
3 Count changed for item 2, POSTing (id=2, count=6) to database. 
4 (useEffect runs twice) Count hasn't changed yet, so don't update the DB (id=2, count=50) 

第3行是不需要的行为:数据库将收到一个带有错误项目ID的帖子,理想情况下根本不应该发送.

这感觉像是一个简单/常见的问题:我应该使用什么设计来防止POST useEffect在过时状态下触发?我很容易想到的所有解决办法在我看来都是荒谬的.(例如, for each 项目创建一个内部组件,只显示其中一个,将所有useEffects组合成一个巨大的useEffect,跟踪组件中的每个状态,等等)我肯定忽略了一些显而易见的事情:有什么 idea 吗?非常感谢.

推荐答案

这个问题是由使用item作为两个useEffect钩子的依赖项引起的.当itemprops 更新第二个useEffect钩子时,第二个useEffect钩子被触发为旧的count状态,然后在第一个useEffect钩子更新count状态后的第二次.

当props 更新时,你基本上只想"重置"InternalComponent状态.只需使用物品的id作为InternalComponent上的react 键.

例子:

<InternalComponent key={item.id} item={item} />

然后,键更改React会丢弃(unmounts)前一个实例,并装载一个新实例,新实例的新状态将按预期初始化.

由于InternalComponent已重新安装/重置,第二个useEffect挂钩现在完全无关,可以移除.状态值将拾取新的itemprops 值.

const InternalComponent = ({ item }) => {
  const [count, setCount] = useState(item.count);
  const [countChanged, setCountChanged] = useState(false);

  useEffect(() => {
    if (countChanged) {
      console.log(
        `Count changed for item ${item.id}, POSTing (id=${item.id}, count=${count}) to database.`
      );
    } else {
      console.log(
        `Count hasn't changed yet, so don't update the DB (id=${item.id}, count=${count})`
      );
    }
  }, [item, count, countChanged]);

  const handleButtonClick = () => {
    setCount(count + 1);
    setCountChanged(true);
  };

  return (
    <div>
      I'm showing item {item.id}, which has count {count}.<br />
      <button onClick={handleButtonClick}>Increment item count</button>
    </div>
  );
};

Edit avoiding-stale-state-in-double-useeffect

Reactjs相关问答推荐

在Redux工具包查询中,是否可以将标记应用到API切片内的所有端点?

为什么我的标签在Redux API中不能正常工作?

在Reaction中单击时,按钮未按预期工作

Reaction Axios多部分/表单-数据数组不工作

导致useState中断的中断模式

在url中使用MongoDB对象ID被认为是不好的做法?

无法找出状态正在被更改的位置

如何在我的 React 应用程序中仅显示我的 Register 组件

如何使用 App Router 和服务器组件在 Next.js 13 中实现分页

如何在react 中过滤数组的数组?

React-router动态路径

无法读取未定义的属性(阅读 'editQuoteModalShown')reactredux

如何在 React 过滤器中包含价格过滤器逻辑?

Material UI @mui/material/Button vs @material-ui/core/Button,它什么时候改变的,旧版本的文档在哪里

根据其中的值不同地react setState 更新状态

在 Nextjs13 应用程序目录中呈现从 supabase 获取的数据

React 测试库 - 如何断言异步函数之间的中间状态?

如何通过onpress on view in react native flatlist来降低和增加特定视图的高度?

Select MenuItem 时 Material-ui 不更改样式

由于另一个子组件,如何在react 中渲染另一个子组件