简体   繁体   English

调用时 React Hook useCallback 未更新 state

[英]React Hook useCallback not getting updated state when invoked

I am developing this application where I double click the page and create a new item.我正在开发这个应用程序,我双击页面并创建一个新项目。 Then I notify a websockets server to broadcast this creation to all other users listening to it.然后我通知 websockets 服务器将这个创建广播给所有其他收听它的用户。 On the other user end I must add this new item, so everybody gets synchronized.在另一个用户端,我必须添加这个新项目,所以每个人都会同步。

I am using react hooks and this is my first app from the scratch using it.我正在使用反应钩子,这是我从头开始使用它的第一个应用程序。 I am having several problems like this in this application, but I selected this flow to illustrate the basic problem.我在这个应用程序中遇到了几个类似的问题,但我选择了这个流程来说明基本问题。 It must cover most issues.它必须涵盖大多数问题。

When I call the useCallback to add the item on the "other user" side, the items state is old (empty).当我调用useCallback在“其他用户”端添加项目时, items state 是旧的(空)。 So no matter how many times I create items, I will only have one on the page, the one I just created.因此,无论我创建多少次项目,页面上都只会有一个,即我刚刚创建的那个。

This is a sample of the code:这是代码示例:

import React, { useEffect, useState, useCallback } from 'react';
import io from 'socket.io-client';

let socket;

function App() {
  const [items, setItems] = useState({});

  const createItem = useCallback((data) => {
    const id = generateId();
    const newItem = { id, ...data };
    const newItems = { ...items, [id]: newItem };

    setItems(newItems)

    // When I create a new item, I notify the server to broadcast it
    socket.emit('itemCreated', newItem)
  }, [items, setItems])

  const doCreateItem = useCallback((item) => {
    // When someone else creates an item, it should be invoked be invoked
    const newItems = { ...items, [item.id]: item };
    setItems(newItems)
  }, [items]);

  useEffect(() => {
    // Connects to the server
    socket = io('ws://localhost:8090', {
      transports: ['websocket'],
      path: '/ws'
    });

    // Listens to event
    socket.on('createitem', (data) => {
      doCreateItem(data)
    });

    // I cannot add doCreateItem here, otherwise it will call this effect everytime doCreateItem is executed
  }, []);

  return (
    <div
      onDoubleClick={
        (e) => {
          createItem({});
        }
      }
    >

      {
        Object.values(items).map(item => {
          return (
            <div>{item.id}</div>
          )
        })
      }

    </div>
  );
}

export default App;

The doCreateItem has the items dependency. doCreateItem具有项目依赖项。 One the other hand, the useEffect dependency list doesn't contain doCreateItem mostly because it will reinvoke the useEffect callback everytime I receive a new item from the server.另一方面, useEffect依赖列表不包含doCreateItem主要是因为它会在我每次从服务器收到新项目时重新调用useEffect回调。 And also because I want this effect to be executed only once (on component mounting).也因为我希望这个效果只执行一次(在组件安装上)。 I tried splitting the useEffect in two, one for the connection and another for the listeners, but the same happens.我尝试将useEffect两部分,一个用于连接,另一个用于侦听器,但同样的情况发生了。

I actually had several problems like this one.我实际上有几个像这样的问题。 A callback is executed more than I was expecting or the state is not updated on the callback.回调执行的次数超出了我的预期,或者 state 未在回调中更新。

Could someone clarify how we should get around this problem?有人可以澄清我们应该如何解决这个问题吗?

Thanks in advance!提前致谢!

Edit编辑

As suggested by @Ross , I changed the setItems calls to receive a callback and return the new state.正如@Ross所建议的,我更改了setItems调用以接收回调并返回新的 state。 That helped with the setItems dependency.这有助于setItems依赖。 But I still can't read items in the doCreateItem function.但我仍然无法读取doCreateItem function 中的items It is always empty.它总是空的。

Something funny that I noticed, is that if I stop using the useCallback and recreate the functions on each render, it doesn't have the updated state on that function, but it does have outside the function:我注意到有趣的是,如果我停止使用useCallback并在每个渲染上重新创建函数,它在 function 上没有更新的 state,但它确实在 ZC1C425268E68385D14ZA 之外:

function App() {
  const [items, setItems] = useState({});
 
  console.log('Rendered again and knows the state >', items)
  const doCreatePostIt = (data) => {
    console.log('Empty >', items)
    // ...
  };
  // ...
}

When the next state depends on the current one, using the functional update form of useState eliminates a dependency in the dependency array and solves the "stale state" problem.当下一个 state 依赖于当前的时,使用useState函数更新形式消除了依赖数组中的一个依赖,解决了“stale state”问题。

  • The setX functions returned from the useState hook is stable and does not change between renders (see useState doc );useState挂钩返回的setX函数是稳定的,不会在渲染之间改变(参见useState文档); it is safe to omit from dependency arrays从依赖项 arrays 中省略是安全的
const [items, setItems] = useState({});

const createItem = useCallback((data) => {
  const id = generateId();
  const newItem = { id, ...data };

  setItems((currItems) => ({
    ...currItems,
    [id]: newItem,
  }))

  socket.emit('itemCreated', newItem)
}, [])

useEffect(() => {
  function doCreateItem(item) {
    setItems((currItems) => ({
      ...currItems,
      [item.id]: item,
    }))
  }

  socket = io('ws://localhost:8090', {
    transports: ['websocket'],
    path: '/ws'
  });

  socket.on('createitem', doCreateItem);

  // Return a clean up function so the component will stop listening to the
  // socket when the component is unmounted.
  return () => {
    socket.off('createItem', doCreateItem)
  }
}, []);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM