【问题标题】:How to use the useState hook with asynchronous calls to change an array?如何使用 useState 钩子和异步调用来更改数组?
【发布时间】:2020-02-01 06:48:32
【问题描述】:

我正在尝试从 put 请求更新数组

const [descriptors, setDescriptors] = useState([]);

const handleDescriptorUpdate = (id, descriptorData) => {
    services
      .putDescriptor(id, descriptorData)
      .then((response) => {
        const descriptorIndex = _.findIndex(descriptors, (e) => e.id === id);
        if (descriptorIndex !== -1) {
          const tempDescriptors = [...descriptors];
          tempDescriptors[descriptorIndex] = response.data;
          setDescriptors(tempDescriptors);
        }
      })
      .catch((error) => {
        console.error(error);
      });
  };

当我当时只执行 1 个请求时,这很好用,但是当我单击执行两次更新的按钮时,而不是传播数组并用新值更新旧值,它们都在传播相同的数组导致当第二个承诺被解决时,它会使用从服务器返回的新值(第二个值)更新状态但是改变第一个值(被第一个承诺改变的那个)为其原始值。

描述符最初由对象数组填充(来自 get 请求):

[
  {
    "id": 24,
    "name": "Test 4.1",
    "percentage": 0,
    "towerId": "5cfc9200-c04a-11e9-89c0-2dd5d3707b1b"
  },
  {
    "id": 23,
    "name": "Test 3.1",
    "percentage": 0,
    "towerId": "5cfc9200-c04a-11e9-89c0-2dd5d3707b1b"
  }
]

【问题讨论】:

  • 欢迎来到 SO!我不认为这真的是一个minimal reproducible example--descriptors 是一个对象数组吗?澄清感谢 - 非常感谢!
  • 这很奇怪,因为 useState 不会立即反映更改,如果有人知道如何强制 useState 同步就好了

标签: javascript reactjs


【解决方案1】:

我看到你让描述符作为状态而不是引用,正如我在上面的评论中所说,useState 不会立即反映更改,所以在内存中保留数组的一个引用,你可以使用钩子 useRef 来做到这一点,请参阅下一个例子:

const [descriptors, setDescriptors] = useState([]);

const descriptorsReference = useRef(null);

const handleDescriptorUpdate = (id, descriptorData) => {
    services
      .putDescriptor(id, descriptorData)
      .then((response) => {
        const descriptorIndex = _.findIndex(descriptors, (e) => e.id === id);
        if (descriptorIndex !== -1) {
         // Use descriptorsReference instead
          const tempDescriptors = [...descriptorsReference.current];
          tempDescriptors[descriptorIndex] = response.data;
          // Next line is to update the descriptors into descriptors state, this phase doesn't happen immediately 'cause is asynchronous 
          setDescriptors(tempDescriptors);
          // Next line is to update the descriptors in memory, this phase occurs immediately 
          descriptorsReference.current = tempDescriptors
        }
      })
      .catch((error) => {
        console.error(error);
      });
  };

【讨论】:

  • @JosephD。请解释为什么在这种情况下使用 ref 不是正确的方法,而不是让一个关于 refs 的链接和一票否决
  • 我可以在功能组件中用非状态变量解决这些存储描述符作为下一个答案,请看一下这个,如果你知道更好的一种方法:stackoverflow.com/a/53146714/6455357
  • 因为它是一种反模式,当您已经有用于此目的的状态挂钩时。使用某些东西,因为它有效意味着just making things happen
【解决方案2】:

尽管React batches all setStates done during an event handler.

setDescriptors 在事件处理程序的范围之外,因为它仅在 promise 解决时调用。

因此,您需要使用state callback 来正确管理描述符版本。

.then((response) => {
  setDescriptors((descriptors) => ( // use state callback
    descriptors.map((desc) =>
      desc.id === id ? { ...desc, ...response.data } : desc
    ) 
  })
}

【讨论】:

  • 这是与原始代码相同的代码,但搜索旧描述符时,会发生错误,因为当调用承诺时,描述符尚未设置,因此描述符不会在旧的描述符上,除了如何你会知道哪个是正确的描述符,每次 id 都是一样的
  • @DariusV,如前所述“描述符最初由对象数组填充(来自获取请求)”。此外,此代码使用以前的状态描述符而不是数组扩展来确保对最新版本的描述符进行任何更新。
  • 我刚刚测试了该代码,但它并没有解决任何问题,因为正如我所说的,在执行 map 时描述符尚未设置,useState 会异步更新状态
  • @DariusV 给我一个你的代码框的链接。 handleDescriptorUpdate 的先决条件是 descriptors 已经通过 get 初始化。您在状态回调中看到的descriptors 是之前的状态。
猜你喜欢
  • 1970-01-01
  • 2020-08-20
  • 2021-11-27
  • 2022-01-08
  • 2021-04-04
  • 2020-08-15
  • 2019-11-15
  • 1970-01-01
  • 2021-04-04
相关资源
最近更新 更多